/*
 * 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 "MemCheck:MemCheck.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "swis.h"
#include "LanMan98BaseLib/strext.h"
#include "LanMan98BaseLib/memory.h"
#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/time.h"
#include "LanMan98BaseLib/callback.h"
#include "LanMan98BaseLib/blocked.h"
#include "LanMan98BaseLib/thread.h"
#include "LanMan98FSLib/fsys_err.h"
#include "LanMan98FSLib/fspatch.h"
#include "date.h"
#include "var.h"
#include "mapping.h"
#include "logon.h"
#include "netbios.h"
#include "discover.h"
#include "smb.h"
#include "vc.h"
#include "smbsvr.h"
#include "printer.h"
#include "nodebug.h"

#include "LanMan98FSLib/fsys.h"
#include "mount.h"

#define MAX_SERVER_NAME_LEN (20)
/*
    The transfer size this filing system advertises.  The layer above asks
    for one block at a time, so this, not the SMB command in use, is what
    decides how many round trips a file costs.  It was 512, which is a
    floppy disc sector and predates every part of the path it now sits on.
*/
/*
    The buffer size handed to FileSwitch when a file is opened.

    It was 4096, which is outside what FileSwitch allows: FSEntry_Open is
    documented as returning "0 if file unbuffered, else must be a power of
    2 between 64 and 1024".  RISC OS 5 does not check, so it worked there
    and nowhere else.  A 26 bit RISC OS does check, and refuses the open
    with error &412, "Bad buffer size" - after this filing system has said
    the open succeeded, so the file appeared to open and was then never
    read from and never closed, and every application reported the same
    unhelpful thing when a file was double clicked.

    1024 is the largest the specification allows.
*/
#define BLOCK_SIZE (1024)

/*
    How often, in centiseconds, an idle connection is prodded so that it
    stays up.

    A hundred seconds is comfortable on a local network, where nothing in
    the middle is counting.  It is not comfortable across anything else: a
    NAT or a stateful firewall on the path forgets an idle mapping on its
    own schedule, and the shorter ones drop it well inside this.  The
    session is then gone without either end having said so, and the next
    operation fails rather than the mount reconnecting quietly.

    LanMan98$KeepAlive overrides it.  Over a wider network something around
    3000 - half a minute - sits inside the usual mapping lifetimes; a
    keepalive is one echo and its reply, so the cost of being early is
    close to nothing and the cost of being late is a dropped session.
*/
#define KEEPALIVE_PERIOD (10000)

/*
    How often, in centiseconds, the directory a viewer was last opened on
    is re-read to see whether anybody else has changed it, and how long to
    wait before trying again when the filing system is busy.
*/
#define WATCH_PERIOD (500)
#define WATCH_BUSY_RETRY (20)

/*
    How often, in centiseconds, the network is asked what is on it, how
    long the answers are collected for, and how often that collecting is
    looked in on.  See sweep_tick().
*/
#define SWEEP_PERIOD (30000)
#define SWEEP_LISTEN (300)
#define SWEEP_TICK (25)
#define SWEEP_BUSY_RETRY (100)
/*
    How often the switch itself is looked at while the sweep is off.  It
    has to be much shorter than the sweep period or turning the sweep back
    on would appear to do nothing for minutes; all it costs meanwhile is
    reading one variable.
*/
#define SWEEP_OFF_RETRY (1000)

struct fsys_image_s
{
    smb_server_t smb_server;
    thread_t keepalive;
    int store_file_types;
    int respect_hidden;
    int bad_reconnect;
    int reconnect_time;
    /* Watching a directory for changes made elsewhere - see dirwatch(). */
    thread_t watch;
    void *watch_ctx;
    int watch_abort;
    char *watch_name;
    char *watch_special;
    unsigned int watch_sig;
    int watch_known;
};

struct fsys_object_s
{
    smb_obj_t smb_obj;
    int refs;
    int dirty;
    int ro_type;
    char *ro_name;
    fsys_image_t img;
};

struct fsys_handle_s
{
    int is_printer;
    int fd;
    smb_handle_t smb_handle;
    fsys_object_t obj;
};

char *FsysFSName = "LanMan98";

int   FsysFSNumber = 156;

//int   FsysFlags = (FSYS_FLAG_SPECIAL_FIELDS | FSYS_FLAG_IMAGE_FILES | FSYS_FLAG_NULL_PATHS);
int   FsysFlags = (int)(FSYS_FLAG_SPECIAL_FIELDS | FSYS_FLAG_NULL_PATHS);

char *FsysStartText = "LanMan98 starting";

fsys_image_t FsysImageFromFileHandle(int x)
{
    Error("Not an image filing system");
    return NULL;
}


#if 1
static void show_error(int *id, char *tag, _kernel_oserror *err)
{
    static char buf[32];

    sprintf(buf, "LanMan98$%sError%d", tag, (*id)++);
    VarSet(buf, err->errmess);
}
#endif

void FsysShut(void)
{
    mount_t mt;

    FsysSweepStop();
    PrinterCloseAll();
    while((mt = FsysNthMount(0)) != NULL)
        FsysDismount(mt);
}



struct mount_s
{
    int full_address;
    tcp_port_t port;        /* as written in the mount, 0 if not given */
    tcp_port_t live_port;   /* as actually connected, for reconnection */
    char *name;
    char *addr;
    char *server;
    char *path;
    char *share;
    char *user;
    char *passwd;
    fsys_image_t img;
    mount_t link;
};

static fsys_image_t user_of_file_descriptor[256] = {NULL};

static void close_all_open_files(fsys_image_t img)
{
    int i;

    for(i = 1; i <256; i++)
        if(user_of_file_descriptor[i] == img)
            _swix(OS_Find, _IN(0)|_IN(1), 0, i);
}

static mount_t mounts = NULL;

static mount_t mount(void)
{
    mount_t ml;

    ml = Malloc(sizeof(*ml));
    memset(ml, 0, sizeof(*ml));
    return ml;
}

static void mount_destruct(mount_t ml)
{
    static char buf[80];

    sprintf(buf, "Filer_CloseDir LanMan98::%s.$", ml->name);
    _kernel_oscli(buf);
    sprintf(buf, "Filer_CloseDir LanMan98#notypes::%s.$", ml->name);
    _kernel_oscli(buf);
    if(ml->name) Free(ml->name);
    if(ml->addr) Free(ml->addr);
    if(ml->server) Free(ml->server);
    if(ml->path) Free(ml->path);
    if(ml->share) Free(ml->share);
    if(ml->user) Free(ml->user);
    if(ml->passwd) Free(ml->passwd);
    if(ml->img)
    {
        close_all_open_files(ml->img);
        FsPatchDropCaches();
        FsysKillImage(ml->img);
    }
    Free(ml);
}

static void check_not_already_mounted(char *server, char *path)
{
    mount_t m;

    for(m = mounts; m; m = m->link)
        if(ci_strcmp(m->server, server) == 0 && ci_strcmp(m->path, path) == 0)
            Error("Already connected to this share on this server");
}


fsys_image_t FsysImageFromDiscName(char *disc, char *special)
{
    if(disc)
    {
        mount_t m;

        for(m = mounts; m; m = m->link)
        {
            if(ci_strcmp(m->name, disc) == 0)
            {
                if(special == NULL || *special == 0)
                    m->img->store_file_types = 1;
                else if(ci_strcmp(special, "notypes") == 0)
                    m->img->store_file_types = 0;
                else
                    ErrorNum(ERR_DiscNotFound);
                return m->img;
            }
        }
        ErrorNum(ERR_DiscNotFound);
        return NULL;
    }
    else
    {
        return PrinterImageFromSpecialField(special);
    }
}

static int in_use = 0;

static int keepalive_period(void)
{
    char *var;
    int period;

    period = KEEPALIVE_PERIOD;
    var = VarRead("LanMan98$KeepAlive");
    if(var)
    {
        period = atoi(var);
        Free(var);
    }
    /*
        Nonsense, or an attempt to switch it off, falls back to the
        default: a connection with nothing holding it up is dropped by the
        server, and that is not a state worth letting anybody configure.
    */
    return (period > 0) ? period : KEEPALIVE_PERIOD;
}

static void keepalive(mount_t mt)
{
    mount_t m;
    fsys_image_t img;
    static int id = 0;

    ExceptTry
    {
        if(in_use)
        {
            CallBackAfter((void (*)(void *))keepalive, mt, keepalive_period());
        }
        else
        {
            img = NULL;
            for(m = mounts; m; m = m->link)
                if(mt == m)
                    img = m->img;
            if(img)
            {
                if(img->keepalive == NULL)
                {
                    img->keepalive = Thread((void (*)(void *))SMBKeepAlive, img->smb_server);
                }
                if(ThreadRipe(img->keepalive))
                {
                    ThreadService(img->keepalive);
                }
                if(ThreadHasCompleted(img->keepalive))
                {
                    ExceptTry
                    {
                        ThreadThrowError(img->keepalive);
                    }
                    ExceptCatch
                    {
                        int errnum;

                        errnum = ExceptCaught()->errnum;
                        if(errnum != 0x10002 && errnum != 0x10003)
                        {
                            SMBFlushLine(img->smb_server);
                        }
                    }
                    ThreadDestruct(img->keepalive);
                    img->keepalive = NULL;
                }
                CallBackAfter((void (*)(void *))keepalive, mt, img->keepalive == NULL
                                                                   ? keepalive_period()
                                                                   : 1);
            }
        }
    } ExceptCatch {show_error(&id, "KeepAlive", ExceptCaught());}
}

/*
    Watching a directory for changes made by somebody else.

    FileSwitch raises UpCall_ModifyingFile for every change this machine
    makes, so a Filer viewer follows those without any help from here.
    Nothing announces a change made by another client on the same server:
    the file is simply not there until something re-reads the directory.

    There is no notification to listen for on the wire either.  SMB2 has
    CHANGE_NOTIFY, but it is an asynchronous request - the server answers
    STATUS_PENDING and replies properly much later - and every request this
    client makes is synchronous.  So the directory most recently listed on
    each mount is re-listed on a timer and compared with what it held last
    time, and a difference raises the same UpCall FileSwitch would have
    raised, which the Filer cannot tell apart from a local change.

    The scan runs as a thread serviced from a callback, exactly as the
    keepalive does, and shares the one connection with it.  Anything in the
    foreground must therefore drain it first: finish_keepalive does that
    for both, so every existing call site is already covered.  Draining
    costs at most one round trip rather than a whole enumeration, because
    the scan tests watch_abort between entries.

    The context block belongs to this code, not to the thread, because the
    result has to outlive the thread that computed it; watch_scan must not
    free it.

    LanMan98$WatchPeriod is the interval in centiseconds.  Setting it to 0
    switches the watch off.
*/

struct watch_ctx_s
{
    fsys_image_t img;
    path_t path;
    unsigned int sig;
    int complete;
};

static int watch_period(void)
{
    char *var;
    int period;

    period = WATCH_PERIOD;
    var = VarRead("LanMan98$WatchPeriod");
    if(var)
    {
        period = atoi(var);
        Free(var);
    }
    return period;
}

static void watch_scan(struct watch_ctx_s *ctx)
{
    smb_obj_t obj;
    unsigned int sig;
    char *name;

    ExceptTry
    {
        sig = 0;
        obj = ctx->path->ntok > 1
                  ? SMBPath2Obj(ctx->path, ctx->img->smb_server, "")
                  : SMBRoot(ctx->img->smb_server);
        if(obj)
        {
            obj = SMBIn(obj);
            while(obj && !ctx->img->watch_abort)
            {
                name = SMBGetName(obj);
                /*
                    "/" and "//" are how the two dot entries arrive; they
                    are not shown and their stamps move for reasons that
                    are not a change to the listing.
                */
                if(strcmp(name, "/") != 0 && strcmp(name, "//") != 0)
                {
                    while(*name)
                        sig = sig * 31 + (unsigned char) *name++;
                    sig = sig * 31 + (unsigned int) SMBGetLength(obj);
                    sig = sig * 31 + (unsigned int) SMBGetAttributes(obj);
                    sig = sig * 31 + (unsigned int) SMBGetTime(obj);
                }
                obj = SMBNext(obj);
            }
            if(obj)
            {
                SMBObjDestruct(obj);    /* abandoned part way */
            }
            else
            {
                ctx->sig = sig;
                ctx->complete = 1;
            }
        }
    }
    ExceptCatch
    {
        /*
            A directory that has been removed, or a connection that has
            gone, is not this code's business to report - whatever the user
            does next will raise it properly.
        */
        ThreadOkay();
        ctx->complete = 0;
    }
}

static void watch_tell_filer(fsys_image_t img)
{
    _kernel_swi_regs regs;

    /*
        Reason 6 with the directory's own name is the one route through the
        Filer's handler that dirties the directory itself rather than its
        parent alone: it marks the parent, then the object and its children.
    */
    regs.r[0] = 3;                              /* UpCall_ModifyingFile */
    regs.r[1] = (int) img->watch_name;
    regs.r[2] = (int) "";
    regs.r[6] = (int) (img->watch_special ? img->watch_special : "");
    regs.r[7] = (int) "";
    regs.r[8] = FsysFSNumber;
    regs.r[9] = 6;                              /* upfsfile_Delete */
    _kernel_swi(OS_UpCall, &regs, &regs);
}

/*
    Take a finished scan apart.  may_tell is 0 when this is called from
    inside a filing system operation, where raising an UpCall could re-enter
    this filing system before it has finished what it is doing.
*/
static void watch_reap(fsys_image_t img, int may_tell)
{
    struct watch_ctx_s *ctx;

    if(img->watch == NULL)
        return;
    ExceptTry
    {
        ThreadThrowError(img->watch);
    }
    ExceptCatch {}
    ThreadDestruct(img->watch);
    img->watch = NULL;
    ctx = (struct watch_ctx_s *) img->watch_ctx;
    img->watch_ctx = NULL;
    if(ctx)
    {
        if(ctx->complete)
        {
            if(may_tell && img->watch_known && ctx->sig != img->watch_sig)
                watch_tell_filer(img);
            img->watch_sig = ctx->sig;
            img->watch_known = 1;
        }
        if(ctx->path) FsPatchPathFree(ctx->path);
        Free(ctx);
    }
}

static void finish_dirwatch(fsys_image_t img)
{
    if(img->watch)
    {
        img->watch_abort = 1;
        while(!ThreadHasCompleted(img->watch))
        {
            BlockedWait();
            if(ThreadRipe(img->watch))
                ThreadService(img->watch);
        }
        watch_reap(img, 0);
    }
}

static void watch_start(fsys_image_t img)
{
    struct watch_ctx_s *volatile ctx;

    ctx = Malloc(sizeof(*ctx));
    ctx->img = img;
    ctx->path = NULL;
    ctx->sig = 0;
    ctx->complete = 0;
    ExceptTry
    {
        ctx->path = FsPatchName2Path(img->watch_name, img);
    }
    ExceptCatch
    {
        Free(ctx);
        ctx = NULL;
    }
    if(ctx)
    {
        img->watch_abort = 0;
        img->watch_ctx = ctx;
        img->watch = Thread((void (*)(void *))watch_scan, ctx);
    }
}

static void dirwatch(mount_t mt)
{
    mount_t m;
    fsys_image_t img;
    int period;

    ExceptTry
    {
        if(in_use)
        {
            CallBackAfter((void (*)(void *))dirwatch, mt, WATCH_BUSY_RETRY);
        }
        else
        {
            img = NULL;
            for(m = mounts; m; m = m->link)
                if(mt == m)
                    img = m->img;
            if(img)
            {
                period = watch_period();
                if(img->watch)
                {
                    if(ThreadRipe(img->watch))
                        ThreadService(img->watch);
                    if(ThreadHasCompleted(img->watch))
                        watch_reap(img, 1);
                }
                else if(period > 0 && img->watch_name && img->smb_server
                                                      && img->keepalive == NULL)
                {
                    watch_start(img);
                }
                CallBackAfter((void (*)(void *))dirwatch, mt,
                              img->watch ? 1
                                         : (period > 0 ? period
                                                       : keepalive_period()));
            }
        }
    }
    ExceptCatch
    {
        /* Best effort; a watch that fails must never disturb the desktop. */
    }
}

/*
    Noticing a server that was not there before.

    *Discover98 asks the network what is on it, and everything that shares
    files answers.  That is a thing the user has to think to do, so a
    machine switched on after the desktop started is not seen until they
    do it.  The same question asked on a timer notices by itself.

    The question is one UDP packet to a multicast address and the answers
    come back unbidden, so a sweep costs no connection, no credentials and
    nothing at the other end - which is why it is safe to leave running.
    Enumerating the shares each server offers is not: that needs a
    connection and a logon per server, and repeating that against every
    machine on the network every few minutes is how account lockouts
    happen.  So the sweep reports servers, and asking what any of them
    shares stays where it was, on the user's own *Discover98.

    Nothing here waits.  DiscoverBegin asks and returns, and each callback
    afterwards takes whatever has arrived until the collecting period is
    up; the machine is never held while a sweep is in progress.

    What is new is put in LanMan98$New, a comma separated list of names,
    for the Filer to show and then unset.  A variable rather than a poll
    word because the Filer already wakes twice a second on its own, and
    because it can be read from the command line, which is the only way
    any of this can be watched on a machine with no desktop.

    LanMan98$SweepPeriod is the interval in centiseconds.  Setting it to 0
    switches the sweep off.
*/

typedef struct seen_s
{
    struct seen_s *next;
    char *name;
} *seen_t;

static int sweep_hold;          /* foreground code is using the network */
static seen_t seen;             /* every server this machine has been told about */
static int seen_known;          /* whether that list is worth comparing against */
static sweep_t sweep_run;       /* the sweep in progress, if there is one */
static int sweep_going;         /* whether a callback is outstanding */

static int sweep_period(void)
{
    char *var;
    int period;

    /*
        Off unless asked for.  This talks to the network on its own, in the
        background, which is a new thing for this client to do; until it has
        been proven on real hardware, a machine that does not ask for it
        behaves exactly as it did before.  SWEEP_PERIOD is what to set the
        variable to for the intended behaviour, not what happens by default.
    */
    period = 0;
    var = VarRead("LanMan98$SweepPeriod");
    if(var)
    {
        period = atoi(var);
        Free(var);
    }
    return period;
}

/*
    Where the list of servers already announced is kept between runs.

    Without this every server on the network is new again each time the
    module is loaded, so the first sweep after a reboot would announce the
    lot.  NULL when there is nowhere to write, in which case the list is
    still kept in memory and only the memory of previous runs is lost.
*/
static char *seen_file(void)
{
    static char path[256];
    char *var;

    var = VarRead("LanMan98$SeenFile");
    if(var)
    {
        if(strlen(var) < sizeof(path))
        {
            strcpy(path, var);
            Free(var);
            return path;
        }
        Free(var);
        return NULL;
    }
    var = getenv("Choices$Write");
    if(var == NULL || *var == 0 || strlen(var) + 14 > sizeof(path))
        return NULL;
    sprintf(path, "%s.LanMan98Seen", var);
    return path;
}

static int seen_has(const char *name)
{
    seen_t s;

    for(s = seen; s; s = s->next)
        if(ci_strcmp(s->name, (char *) name) == 0)
            return 1;
    return 0;
}

static void seen_add(const char *name)
{
    seen_t s;

    s = Malloc(sizeof(*s));
    s->name = strdup((char *) name);
    s->next = seen;
    seen = s;
}

static void seen_forget(void)
{
    seen_t s, next;

    for(s = seen; s; s = next)
    {
        next = s->next;
        Free(s->name);
        Free(s);
    }
    seen = NULL;
    seen_known = 0;
}

static void seen_load(void)
{
    char line[128];
    char *path;
    FILE *fp;
    int n;

    path = seen_file();
    if(path == NULL)
        return;
    fp = fopen(path, "r");
    if(fp == NULL)
        return;
    while(fgets(line, sizeof(line), fp))
    {
        n = (int) strlen(line);
        while(n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r'))
            line[--n] = 0;
        if(n > 0 && !seen_has(line))
            seen_add(line);
    }
    fclose(fp);
    /*
        A file that was there at all means these were announced on some
        previous run, so what is not in it now is genuinely new.
    */
    seen_known = 1;
}

static void seen_save(void)
{
    char *path;
    seen_t s;
    FILE *fp;

    path = seen_file();
    if(path == NULL)
        return;
    fp = fopen(path, "w");
    if(fp == NULL)
        return;                 /* nowhere to write is not worth an error */
    for(s = seen; s; s = s->next)
        fprintf(fp, "%s\n", s->name);
    fclose(fp);
}

/*
    Work out what in a finished sweep had not been seen before, remember
    it, and say so.  The first sweep of all only remembers: announcing
    every machine that was already there is not news.
*/
static void sweep_announce(found_t list)
{
    char names[256];
    found_t f;
    int len, n, any;

    names[0] = 0;
    len = 0;
    n = 0;
    any = 0;
    for(f = list; f; f = f->next)
    {
        if(f->name == NULL || *f->name == 0 || seen_has(f->name))
            continue;
        seen_add(f->name);
        any = 1;
        if(!seen_known)
            continue;
        if(len + (int) strlen(f->name) + 2 < (int) sizeof(names))
        {
            if(n++)
                names[len++] = ',';
            strcpy(names + len, f->name);
            len += (int) strlen(f->name);
        }
    }
    if(n)
        VarSet("LanMan98$New", names);
    if(any)
        seen_save();
    seen_known = 1;
}

static void sweep_tick(void *unused)
{
    int next;
    int period;

    (void) unused;
    period = SWEEP_PERIOD;
    next = SWEEP_TICK;
    ExceptTry
    {
        period = sweep_period();
        if(period <= 0)
        {
            /*
                Switched off.  Anything part way through is dropped rather
                than finished, and the chain keeps ticking slowly so that
                switching it back on takes effect without a reload.
            */
            if(sweep_run)
            {
                DiscoverAbandon(sweep_run);
                sweep_run = NULL;
            }
            next = SWEEP_OFF_RETRY;
        }
        else if(in_use || sweep_hold)
        {
            next = SWEEP_BUSY_RETRY;
        }
        else if(sweep_run)
        {
            if(DiscoverPoll(sweep_run))
            {
                next = SWEEP_TICK;
            }
            else
            {
                found_t list;

                list = DiscoverEnd(sweep_run);
                sweep_run = NULL;
                sweep_announce(list);
                DiscoverFree(list);
                next = period;
            }
        }
        else
        {
            sweep_run = DiscoverBegin(SWEEP_LISTEN);
            next = SWEEP_TICK;
        }
    }
    ExceptCatch
    {
        /*
            A network that will not carry the question, or no network at
            all, is the ordinary case on a machine that is not plugged in.
            Drop whatever was in progress and try again later; a sweep that
            fails must never disturb the desktop.
        */
        if(sweep_run)
        {
            DiscoverAbandon(sweep_run);
            sweep_run = NULL;
        }
        next = period > 0 ? period : SWEEP_OFF_RETRY;
    }
    ExceptTry
    {
        CallBackAfter(sweep_tick, NULL, next);
    }
    ExceptCatch
    {
        sweep_going = 0;        /* the chain is broken; FsysSweepStart restarts it */
    }
}

/*
    Keep the sweep out of the way while the foreground is using the network.

    The callback that drives the sweep fires at any safe point, including
    inside a *command that is part way through talking to a server - the
    filing system's own in_use flag does not cover those, because a command
    is not an entry to the filing system.  So a second socket could be
    opened, and read from, underneath one that was already in use.

    Anything the sweep has part way through is dropped rather than left
    open alongside the foreground's own: a sweep is due again in minutes
    and nothing is lost by abandoning this one.
*/
void FsysSweepHold(int on)
{
    if(on)
    {
        sweep_hold++;
        if(sweep_run)
        {
            DiscoverAbandon(sweep_run);
            sweep_run = NULL;
        }
    }
    else if(sweep_hold > 0)
    {
        sweep_hold--;
    }
}

void FsysSweepStart(void)
{
    if(sweep_going)
        return;
    sweep_going = 1;
    seen_load();
    ExceptTry
    {
        CallBackAfter(sweep_tick, NULL, SWEEP_LISTEN);
    }
    ExceptCatch
    {
        sweep_going = 0;
    }
}

void FsysSweepStop(void)
{
    if(sweep_run)
    {
        DiscoverAbandon(sweep_run);
        sweep_run = NULL;
    }
    seen_forget();
    sweep_going = 0;
}


void FsysDirRead(fsys_image_t img, char *name, char *special)
{
    if(img == NULL || name == NULL || PrinterClaimsImage(img))
        return;
    if(img->watch_name
        && strcmp(img->watch_name, name) == 0
        && (img->watch_special == NULL) == (special == NULL)
        && (special == NULL || strcmp(img->watch_special, special) == 0))
        return;                 /* the same directory: keep its signature */

    if(img->watch_name) Free(img->watch_name);
    if(img->watch_special) Free(img->watch_special);
    img->watch_name = NULL;
    img->watch_special = NULL;
    img->watch_known = 0;       /* the first scan only records a baseline */
    ExceptTry
    {
        img->watch_name = strdup(name);
        if(special) img->watch_special = strdup(special);
    }
    ExceptCatch
    {
        if(img->watch_name) Free(img->watch_name);
        img->watch_name = NULL;
        img->watch_special = NULL;
    }
}

static void finish_keepalive(fsys_image_t img)
{
    PrinterImageRefuse(img, 1);
    finish_dirwatch(img);
    if(img->keepalive)
    {
        while(!ThreadHasCompleted(img->keepalive))
        {
            BlockedWait();
            if(ThreadRipe(img->keepalive))
            {
                ThreadService(img->keepalive);
            }
        }
        ExceptTry
        {
            ThreadThrowError(img->keepalive);
        }
        ExceptCatch
        {
            int errnum;

            errnum = ExceptCaught()->errnum;
            if(errnum != 0x10002 && errnum != 0x10003)
            {
                SMBFlushLine(img->smb_server);
            }
        }
        ThreadDestruct(img->keepalive);
        img->keepalive = NULL;
    }
}

static void reconnect(fsys_image_t img)
{
    mount_t mt;

    if(img)
    {
        for(mt = mounts; mt && mt->img != img; mt = mt->link)
            ;
        if(mt)
        {
            close_all_open_files(mt->img);
            FsPatchDropCaches();
            finish_dirwatch(mt->img);
            mt->img->watch_known = 0;   /* the old signature means nothing
                                           against a new connection */
            if(mt->img->keepalive)
            {
                ThreadDestruct(mt->img->keepalive);
                mt->img->keepalive = NULL;
            }
            if(mt->img->smb_server)
            {
                SMBReconnect(mt->img->smb_server, mt->addr,
                                                 mt->live_port,
                                                 mt->server,
                                                 mt->share,
                                                 mt->user,
                                                 mt->passwd);
            }
            else
            {
                mt->img->smb_server = SMBConnect(mt->addr,
                                                 mt->live_port,
                                                 mt->server,
                                                 mt->share,
                                                 mt->user,
                                                 mt->passwd);
            }
            mt->live_port = SMBPort(mt->img->smb_server);
            mt->img->bad_reconnect = 0;
            CallBackAfter((void (*)(void *))keepalive, mt, keepalive_period());
        }
    }
}

static int read3hex(char *p)
{
    int i, n;

    n = 0;
    for(i = 0; i < 3; i++)
    {
        n <<= 4;
        switch(p[i])
        {
            case '0': break;
            case '1': n += 0x1; break;
            case '2': n += 0x2; break;
            case '3': n += 0x3; break;
            case '4': n += 0x4; break;
            case '5': n += 0x5; break;
            case '6': n += 0x6; break;
            case '7': n += 0x7; break;
            case '8': n += 0x8; break;
            case '9': n += 0x9; break;
            case 'a': n += 0xa; break;
            case 'b': n += 0xb; break;
            case 'c': n += 0xc; break;
            case 'd': n += 0xd; break;
            case 'e': n += 0xe; break;
            case 'f': n += 0xf; break;
            default: return -1;
        }
    }
    return p[i] == 0 ? n : -1;
}

static int name2type(char *name)
{
    int type;
    char *p;

    type = -1;
    p = strrchr(name, ',');
    if(p)
        type = read3hex(p+1);
    if(type == -1)
    {
        p = strrchr(name, '/');
        type = MapApply(p ? p+1 : NULL);
    }
    return type;
}

static char *full_name(char *name, int type)
{
    char *p, *res;

    p = strrchr(name, '/');
    if(type == MapApply(p ? p+1 : NULL))
    {
        res = strdup(name);
    }
    else
    {
        res = Malloc(strlen(name) + 5);
        sprintf(res, "%s,%03x", name, type);
    }
    return res;
}

static char *drop_ext(char *name)
{
    char *p, *res;
    int len;

    p = strrchr(name, ',');
    if(p && read3hex(p+1) != -1)
    {
        len = p-name;
        res = Malloc(len+1);
        memcpy(res, name, len);
        res[len] = 0;
    }
    else
    {
        res = strdup(name);
    }
    return res;
}

static int has_ext(char *name)
{
    char *p;

    p = strrchr(name, ',');
    return p && read3hex(p+1) != -1;
}

void FsysEnter(void)
{
    static _kernel_oserror reent_err = {0x100A0, "LanMan98 in use"};

    if(in_use)
        ErrorErr(&reent_err);
    in_use = 1;
}

void FsysLeave(void)
{
    in_use = 0;
}

mount_t FsysFindMount(char *name)
{
    mount_t m;

    for(m = mounts; m; m = m->link)
        if(ci_strcmp(m->name, name) == 0)
            return m;
    return NULL;
}

mount_t FsysNthMount(int i)
{
    mount_t m;

    for(m = mounts; m; m = m->link)
        if(i-- == 0)
            return m;
    return NULL;
}

mount_t FsysMount(char *name, char *server_addr, char *path, char *user, char *passwd)
{
    mount_t mt;
    char *addr;
    int l;
    char server[MAX_SERVER_NAME_LEN+1];

    // split up server name and addr
    addr = strchr(server_addr, '<');
    if(addr && addr[strlen(addr)-1] != '>')
        Error("Incorrectly specified server IP address.  Put the IP address, optionally followed by :port, in angle brackets after the NetBIOS name.");
    l = addr ? addr - server_addr : strlen(server_addr);
    if(l > MAX_SERVER_NAME_LEN)
        Error("Server names are never that long");
    memcpy(server, server_addr, l);
    server[l] = 0;

    user = user ? user : "";
    passwd = passwd ? passwd : "";
    if(*user == 0 && *passwd == 0)
    {
        user = LoggedOnUser(server);
        passwd = LoggedOnPassword(server);
    }
    mt = FsysFindMount(name);
    if(mt) FsysDismount(mt);
    mt = mount();
    ExceptTry
    {
        mt->name = strdup(name);
        mt->server = strdup(server);
        mt->path = strdup(path);
        mt->user = strdup(user);
        mt->passwd = strdup(passwd);
        if(addr)
        {
            char *colon;

            // Remove <>s from addr while copying it
            mt->addr = strdup(addr+1);
            mt->addr[strlen(mt->addr)-1] = 0;
            // An optional :port may follow the address
            colon = strchr(mt->addr, ':');
            if(colon)
            {
                *colon = 0;
                mt->port = VCParsePort(colon+1);
            }
            if(*mt->addr)
            {
                mt->full_address = 1;
            }
            else
            {
                // <:445> names a port but leaves the address to be looked up
                Free(mt->addr);
                mt->addr = NetBIOSResolve(mt->server);
            }
        }
        else
        {
            mt->addr = NetBIOSResolve(mt->server);
        }
        check_not_already_mounted(mt->server, mt->path);
        mt->share = Malloc(strlen(mt->server) + strlen(mt->path) + 4);
        sprintf(mt->share, "\\\\%s\\%s", mt->server, mt->path);
        mt->img = Malloc(sizeof(*mt->img));
        memset(mt->img, 0, sizeof(*mt->img));
        mt->img->store_file_types = 1;
        {
            char *hvar;

            hvar = VarRead("LanMan98$ShowHidden");
            mt->img->respect_hidden = (hvar == NULL);
            Free(hvar);
        }
        mt->img->smb_server = SMBConnect(mt->addr,
                                         mt->port,
                                         mt->server,
                                         mt->share,
                                         mt->user,
                                         mt->passwd);
        mt->live_port = SMBPort(mt->img->smb_server);
        CallBackAfter((void (*)(void *))keepalive, mt, keepalive_period());
        CallBackAfter((void (*)(void *))dirwatch, mt, WATCH_PERIOD);
    }
    ExceptCatch
    {
        mount_destruct(mt);
        ExceptRethrow();
    }
    mt->link = mounts;
    mounts = mt;
    LogMount(mt->server, mt->user, mt->passwd);
    return mt;
}

void FsysDismount(mount_t mt)
{
    mount_t *p;

    p = &mounts;
    while(*p)
    {
        if(*p == mt)
        {
            *p = (*p)->link;
            LogDismount(mt->server);
            mount_destruct(mt);
        }
        else
        {
            p = &(*p)->link;
        }
    }
    if(mounts == NULL)
        ThreadLibraryFinalise();
}

char *FsysMountName(mount_t mt)
{
    mount_t m;

    for(m = mounts; m; m = m->link)
        if(mt == m)
            return m->name;
    return NULL;
}

char *FsysMountServer(mount_t mt)
{
    mount_t m;
    static char buf[512];
    char port[12];
    int len;

    for(m = mounts; m; m = m->link)
    {
        if(mt == m)
        {
            if(m->port)
                sprintf(port, ":%d", m->port);
            else
                port[0] = 0;
            len = strlen(m->server) + strlen(port) + 3;
            if(m->full_address)
                len += strlen(m->addr);
            if((m->full_address || m->port) && len <= 512)
            {
                sprintf(buf, "%s<%s%s>", m->server,
                             m->full_address ? m->addr : "", port);
                return buf;
            }
            else
            {
                return m->server;
            }
        }
    }
    return NULL;
}

char *FsysMountPath(mount_t mt)
{
    mount_t m;

    for(m = mounts; m; m = m->link)
        if(mt == m)
            return m->path;
    return NULL;
}

char *FsysMountUser(mount_t mt)
{
    mount_t m;

    for(m = mounts; m; m = m->link)
        if(mt == m)
            return m->user;
    return NULL;
}

void FsysKillImage(fsys_image_t img)
{
    PrinterImageRefuse(img, 2);
    finish_keepalive(img);
    if(img->watch_name) Free(img->watch_name);
    if(img->watch_special) Free(img->watch_special);
    img->watch_name = NULL;
    img->watch_special = NULL;
    if(img->smb_server) SMBDropServer(img->smb_server);
    if(img->keepalive) ThreadDestruct(img->keepalive);
    Free(img);
}

void FsysStamp(fsys_image_t img, int now)
{
    PrinterImageRefuse(img, 3);
}

void FsysFlushImage(fsys_image_t img)
{
    PrinterImageRefuse(img, 4);
}

static void mul32x32_to_64(unsigned int x, unsigned int y, int64 *res)
{
    unsigned int ll, lh, hl, hh;

    ll = (x & 0xffff) * (y & 0xffff);
    lh = (x & 0xffff) * (y >> 16);
    hl = (x >> 16) * (y & 0xffff);
    hh = (x >> 16) * (y >> 16);
    res->high = hh + (lh >> 16) + (hl >> 16);
    hl = (hl & 0xffff) + (lh & 0xffff) + (ll >> 16);
    res->high += (hl >> 16);
    res->low = (hl << 16) | (ll & 0xffff);
}

void FsysSpace(fsys_image_t img, int64 *total, int64 *free)
{
    smb_disc_info_t dinfo;

    PrinterImageRefuse(img, 5);
    finish_keepalive(img);
    SMBDiskInfo(img->smb_server, &dinfo);
    mul32x32_to_64(dinfo.units, dinfo.free, free);
    mul32x32_to_64(dinfo.units, dinfo.total, total);
}

char *FsysGetDiscName(fsys_image_t img)
{
    PrinterImageRefuse(img, 6);
    return "";
}

void FsysSetDiscName(fsys_image_t img, char *name)
{
    PrinterImageRefuse(img, 7);
}

char *FsysDiscType(fsys_image_t img)
{
    PrinterImageRefuse(img, 8);
    return "CIFS";
}

int FsysDiscCycleId(fsys_image_t img)
{
    PrinterImageRefuse(img, 9);
    return 0;
}


char *FsysNameMap(char *name, fsys_image_t img)
{
    PrinterImageRefuse(img, 10);
    return SMBNameMap(name, img->smb_server);
}


fsys_object_t FsysCreateFile(fsys_object_t obj, char *name)
{
    fsys_object_t nobj;

    PrinterObjectRefuse(obj, 1);
    finish_keepalive(obj->img);
    nobj = Malloc(sizeof(*nobj));
    nobj->ro_name = NULL;
    nobj->ro_type = name2type(name);
    nobj->dirty = 1;
    nobj->refs = 1;
    nobj->smb_obj = NULL;
    nobj->img = obj->img;
    ExceptTry
    {
        nobj->smb_obj = SMBCreateFile(obj->smb_obj, name);
    }
    ExceptCatch
    {
        FsysFree(nobj);
        ExceptRethrow();
    }
    return nobj;
}

fsys_object_t FsysCreateDirectory(fsys_object_t obj, char *name)
{
    fsys_object_t nobj;

    PrinterObjectRefuse(obj, 2);
    finish_keepalive(obj->img);
    nobj = Malloc(sizeof(*nobj));
    nobj->ro_name = NULL;
    nobj->ro_type = name2type(name);
    nobj->dirty = 1;
    nobj->refs = 1;
    nobj->smb_obj = NULL;
    nobj->img = obj->img;
    ExceptTry
    {
        nobj->smb_obj = SMBCreateDirectory(obj->smb_obj, name);
    }
    ExceptCatch
    {
        FsysFree(nobj);
        ExceptRethrow();
    }
    return nobj;
}

void FsysRename(fsys_object_t obj, char *name)
{
    char *ext_name;

    PrinterObjectRefuse(obj, 3);
    finish_keepalive(obj->img);
    if(obj->ro_name)
    {
        Free(obj->ro_name);
        obj->ro_name = NULL;
    }
    DEBUG_PRINT1("Store types: %d", obj->img->store_file_types);
    DEBUG_PRINT1("Name: %s", name);
    DEBUG_PRINT1("RO type: 0x%03x", obj->ro_type);

    ext_name = obj->img->store_file_types
                    ? full_name(name, obj->ro_type)
                    : strdup(name);
    DEBUG_PRINT1("%s", ext_name);
    ExceptTry
    {
        SMBRename(obj->smb_obj, ext_name);
    }
    ExceptCatch
    {
        Free(ext_name);
        ExceptRethrow();
    }
    Free(ext_name);
}

void FsysMove(fsys_object_t obj1, fsys_object_t obj2)
{
    PrinterObjectRefuse(obj1, 4);
    PrinterObjectRefuse(obj2, 5);
    finish_keepalive(obj1->img);
    SMBMove(obj1->smb_obj, obj2->smb_obj);
}

void FsysDelete(fsys_object_t obj)
{
    PrinterObjectRefuse(obj, 6);
    finish_keepalive(obj->img);
    SMBDelete(obj->smb_obj);
}

static int hidden(fsys_object_t obj)
{
    return obj->img->respect_hidden && (SMBGetAttributes(obj->smb_obj) & SMB_HIDDEN);
}

static int looks_like_a_dos_name(char *name)
{
    char *p;

    DEBUG_PRINT1("Name = %s", name);
    /* See if there's a ~ */
    p = strrchr(name, '~');

    /* If not, its noe a DOS name */
    if(p == NULL)
        return 0;

    /* Jump over ~ and check the rest is digits */
    for(p++; *p; p++)
        if(!isdigit(*p))
            return 0;

    return 1;
}

static void extension_search(path_t path, fsys_object_t obj)
{
    if (obj->img->smb_server->nas_basic_firmware)
    {
        /* This firmware does not support wildcards in the search other than
           a leaf of exactly "*", so we must search the entire directory */
        char *leaf = path->tok[path->ntok - 1];
        int len = strlen(leaf);

        path->tok[path->ntok - 1] = "";

        ExceptTry
        {
            obj->smb_obj = SMBPath2Obj(path, obj->img->smb_server, "*");
            while(obj->smb_obj)
            {
                char *name = SMBGetName(obj->smb_obj);
                if (has_ext(name) && (strlen(name) == (len + 4)) && (strncmp(name, leaf, len) == 0))
                    break;
                obj->smb_obj = SMBNext(obj->smb_obj);
            }
        }
        ExceptCatch
        {
            path->tok[path->ntok - 1] = leaf;
            ExceptRethrow();
        }
        path->tok[path->ntok - 1] = leaf;
    }
    else
    {
        obj->smb_obj = SMBPath2Obj(path, obj->img->smb_server, ",###");
        while(obj->smb_obj && !has_ext(SMBGetName(obj->smb_obj)))
            obj->smb_obj = SMBNext(obj->smb_obj);
    }
}

static fsys_object_t fsys_path2object(path_t path, fsys_image_t img)
{
    fsys_object_t obj;
    int errnum;

    obj = Malloc(sizeof(*obj));
    obj->refs = 1;
    obj->ro_name = NULL;
    obj->smb_obj = NULL;
    obj->img = img;
    ExceptTry
    {
        if(path->ntok > 1)
        {
            if(looks_like_a_dos_name(path->tok[path->ntok-1]))
            {
               /*
                * Stupid Windows might give us the DOS name, and cause us
                * to miss the full name with ,xxx extension.  So we are
                * forced to do the slow directory enumeration first.
                */
                DEBUG_PRINT0("Looks like DOS");
                ExceptTry
                {
                    if(obj->smb_obj == NULL && obj->img->store_file_types)
                    {
                        extension_search(path, obj);
                    }
                }
                ExceptCatch
                {
                    errnum = ExceptCaught()->errnum;
                    if(errnum != 0x10002 && errnum != 0x10003)
                        ExceptRethrow();
                }
                if(obj->smb_obj == NULL)
                    obj->smb_obj = SMBPath2Obj(path, img->smb_server, "");
            }
            else
            {
               /*
                * No way is this a DOS name, so its safe to do the fast
                * direct request for the file.
                */
                DEBUG_PRINT0("Looks like non-DOS");
                ExceptTry
                {
                    obj->smb_obj = SMBPath2Obj(path, img->smb_server, "");
                }
                ExceptCatch
                {
                    errnum = ExceptCaught()->errnum;
                    if(errnum != 0x10002 && errnum != 0x10003)
                        ExceptRethrow();
                }
                if(obj->smb_obj == NULL && obj->img->store_file_types)
                {
                    extension_search(path, obj);
                }
            }
        }
        else
        {
            SMBKeepAlive(img->smb_server);
            obj->smb_obj = SMBRoot(img->smb_server);
            obj->ro_type = 0xFFD;
            obj->dirty = 0;
        }
        if(obj->smb_obj == NULL)
        {
            FsysFree(obj);
            obj = NULL;
        }
        else
        {
            obj->ro_type = name2type(SMBGetName(obj->smb_obj));
            obj->dirty = 0;
        }
    }
    ExceptCatch
    {
        FsysFree(obj);
        obj = NULL;
        errnum = ExceptCaught()->errnum;
        if(errnum != 0x10002 && errnum != 0x10003)
        {
            // show_error(&id, "Get", ExceptCaught());
            ExceptRethrow();
        }
    }
    return obj;
}

fsys_object_t FsysPath2Object(path_t path, fsys_image_t img)
{
    fsys_object_t res;

    if(PrinterClaimsImage(img))
        return PrinterPath2Object(path, img);
    finish_keepalive(img);

    if(img->bad_reconnect && Time() - img->reconnect_time < 300)
        Error("Connection down");
    ExceptTry
    {
        res = fsys_path2object(path, img);
    }
    ExceptCatch
    {
        ExceptTry
        {
            reconnect(img);
            res = fsys_path2object(path, img);
        }
        ExceptCatch
        {
            img->bad_reconnect = 1;
            img->reconnect_time = Time();
            ExceptRethrow();
        }
    }
    img->bad_reconnect = 0;
    return res;
}

fsys_object_t FsysImgRoot(fsys_image_t img)
{
    fsys_object_t obj;

    PrinterImageRefuse(img, 11);
    finish_keepalive(img);
    obj = Malloc(sizeof(*obj));
    obj->refs = 1;
    obj->ro_name = NULL;
    obj->smb_obj = SMBRoot(img->smb_server);
    obj->ro_type = 0xFFD;
    obj->dirty = 0;
    obj->img = img;
    return obj;
}

static int dots(fsys_object_t obj)
{
    char *s;

    s = SMBGetName(obj->smb_obj);
    return strcmp(s, "/") == 0 || strcmp(s, "//") == 0;
}

fsys_object_t FsysIn(fsys_object_t obj)
{
    PrinterObjectRefuse(obj, 7);
    finish_keepalive(obj->img);
    if(obj->ro_name)
    {
        Free(obj->ro_name);
        obj->ro_name = NULL;
    }
    obj->smb_obj = SMBIn(obj->smb_obj);
    while(obj->smb_obj && (dots(obj)||hidden(obj)))
        obj->smb_obj = SMBNext(obj->smb_obj);
    if(obj->smb_obj)
    {
        obj->dirty = 0;
        obj->ro_type = name2type(SMBGetName(obj->smb_obj));
        return obj;
    }
    else
    {
        FsysFree(obj);
        return NULL;
    }
}

fsys_object_t FsysNext(fsys_object_t obj)
{
    PrinterObjectRefuse(obj, 8);
    finish_keepalive(obj->img);
    if(obj->ro_name)
    {
        Free(obj->ro_name);
        obj->ro_name = NULL;
    }
    obj->smb_obj = SMBNext(obj->smb_obj);
    while(obj->smb_obj && (dots(obj)||hidden(obj)))
        obj->smb_obj = SMBNext(obj->smb_obj);
    if(obj->smb_obj)
    {
        obj->dirty = 0;
        obj->ro_type = name2type(SMBGetName(obj->smb_obj));
        return obj;
    }
    else
    {
        FsysFree(obj);
        return NULL;
    }
}

fsys_object_t FsysDup(fsys_object_t obj)
{
    fsys_object_t nobj;

    if(PrinterClaimsObject(obj))
        return obj;
    nobj = Malloc(sizeof(*nobj));
    nobj->refs = 1;
    nobj->ro_name = NULL;
    nobj->smb_obj = obj->smb_obj ? SMBObjDup(obj->smb_obj) : NULL;
    nobj->dirty = 0;
    nobj->ro_type = name2type(SMBGetName(obj->smb_obj));
    nobj->img = obj->img;
    return nobj;
}

fsys_object_t FsysNewRef(fsys_object_t obj)
{
    if(PrinterClaimsObject(obj))
        return obj;
    ++(obj->refs);
    return obj;
}

void FsysFree(fsys_object_t obj)
{
    if(PrinterClaimsObject(obj))
        return;
    finish_keepalive(obj->img);
    if(--(obj->refs) == 0)
    {
        if(obj->ro_name)
            Free(obj->ro_name);
        if(obj->smb_obj)
            SMBObjDestruct(obj->smb_obj);
        Free(obj);
    }
}



char *FsysGetName(fsys_object_t obj)
{
    PrinterObjectRefuse(obj, 9);
    finish_keepalive(obj->img);
    if(obj->ro_name == NULL)
        obj->ro_name = obj->img->store_file_types
                            ? drop_ext(SMBGetName(obj->smb_obj))
                            : strdup(SMBGetName(obj->smb_obj));
    return obj->ro_name;
}

int FsysGetLength(fsys_object_t obj)
{
    if(PrinterClaimsObject(obj))
        return 0;
    finish_keepalive(obj->img);
    return SMBGetLength(obj->smb_obj);
}

void FsysSetLength(fsys_object_t obj, int l)
{
    if(PrinterClaimsObject(obj))
        return;
    // PrinterObjectRefuse(obj, 10);
    finish_keepalive(obj->img);
    SMBSetLength(obj->smb_obj, l);
}

int FsysGetLoadAddr(fsys_object_t obj)
{
    int arc[2];

    if(PrinterClaimsObject(obj))
        return (int)0xFFFFFF00;
    finish_keepalive(obj->img);
    DateSMBtoARC(SMBGetTime(obj->smb_obj), arc);
    return 0xFFF00000 | (obj->ro_type << 8) | arc[1];
}

void FsysSetLoadAddr(fsys_object_t obj, int laddr)
{
    int arc[2];

    PrinterObjectRefuse(obj, 11);
    finish_keepalive(obj->img);
    if((laddr & 0xFFF00000) == 0xFFF00000)
    {
        DateSMBtoARC(SMBGetTime(obj->smb_obj), arc);
        arc[1] = (laddr & 0xFF);
        SMBSetTime(obj->smb_obj, DateARCtoSMB(arc));
        obj->ro_type = ((laddr >> 8) & 0xFFF);
        obj->dirty = 1;
    }
}

int FsysGetExecAddr(fsys_object_t obj)
{
    int arc[2];

    if(PrinterClaimsObject(obj))
        return 0;
    finish_keepalive(obj->img);
    DateSMBtoARC(SMBGetTime(obj->smb_obj), arc);
    return arc[0];
}

void FsysSetExecAddr(fsys_object_t obj, int eaddr)
{
    int arc[2];

    PrinterObjectRefuse(obj, 12);
    finish_keepalive(obj->img);
    DateSMBtoARC(SMBGetTime(obj->smb_obj), arc);
    arc[0] = eaddr;
    SMBSetTime(obj->smb_obj, DateARCtoSMB(arc));
}

int FsysGetAttributes(fsys_object_t obj)
{
    int attrs, dos_attrs;

    if(PrinterClaimsObject(obj))
        return FSYS_READ | FSYS_WRITE;
    finish_keepalive(obj->img);
    dos_attrs = SMBGetAttributes(obj->smb_obj);
    attrs = 3;
    if(dos_attrs & SMB_READONLY)
    {
        attrs |= 0x008;
        attrs &= ~0x002;
    }
    if(dos_attrs & SMB_HIDDEN) attrs |= 0x100;
    if(dos_attrs & SMB_SYSTEM) attrs |= 0x200;
    if(dos_attrs & SMB_ARCHIVE) attrs |= 0x400;
    return attrs;
}

void FsysSetAttributes(fsys_object_t obj, int att)
{
    int dattr;

    PrinterObjectRefuse(obj, 13);
    finish_keepalive(obj->img);
    dattr = (SMBGetAttributes(obj->smb_obj) & SMB_SUBDIR);
    if(att & 0x008) dattr |= SMB_READONLY;
    /*
    if(att & 0x100) dattr |= SMB_HIDDEN;
    if(att & 0x200) dattr |= SMB_SYSTEM;
    if(att & 0x400) dattr |= SMB_ARCHIVE;
    */
    SMBSetAttributes(obj->smb_obj, dattr);
}

int FsysGetType(fsys_object_t obj)
{
    if(PrinterClaimsObject(obj))
        return FSYS_FILE;
    finish_keepalive(obj->img);
    return SMBGetAttributes(obj->smb_obj) & SMB_SUBDIR
                 ? FSYS_DIRECTORY
                 : FSYS_FILE;
}

/*
    The buffer size FileSwitch is told to use for an open file.

    FileSwitch allocates a buffer of this size for every file that is open,
    and the size has to be a power of two.  Four kilobytes suits a network
    filing system - it is the smallest that does not make reading a byte at
    a time absurd - and is what this has always returned.

    LanMan98$BlockSize overrides it, for finding out whether a machine that
    opens a file and then does nothing with it is failing to get the buffer.
    Zero says the file is not to be buffered at all, which is slower for
    reading a byte at a time but asks FileSwitch for no memory.  Anything
    that is not a power of two, or is bigger than 64K, is ignored.
*/
int FsysGetBlockSize(fsys_object_t obj)
{
    char *var;
    int n;

    var = getenv("LanMan98$BlockSize");
    if(var != NULL)
    {
        n = (int) strtol(var, NULL, 0);
        if(n == 0)
            return 0;                   /* not buffered at all */
        /* Only what FileSwitch will accept: a power of two, 64 to 1024 */
        if((n >= 64) && (n <= 1024) && ((n & (n - 1)) == 0))
            return n;
    }
    return BLOCK_SIZE;
}

void FsysFlushObject(fsys_object_t obj)
{
    if(PrinterClaimsObject(obj))
        return;
    finish_keepalive(obj->img);
    if(obj->dirty && (SMBGetAttributes(obj->smb_obj) & SMB_SUBDIR) == 0)
    {
        if(obj->img->store_file_types && obj->ro_type != name2type(SMBGetName(obj->smb_obj)))
        {
            char *ext_name, *name;

            name = obj->img->store_file_types
                        ? drop_ext(SMBGetName(obj->smb_obj))
                        : strdup(SMBGetName(obj->smb_obj));
            ExceptTry
            {
                ext_name = full_name(name, obj->ro_type);
            }
            ExceptCatch
            {
                Free(name);
                ExceptRethrow();
            }
            Free(name);
            ExceptTry
            {
                SMBRename(obj->smb_obj, ext_name);
            }
            ExceptCatch
            {
                Free(ext_name);
                ExceptRethrow();
            }
            Free(ext_name);
        }
    }
    obj->dirty = 0;
    ExceptTry
    {
        SMBFlush(obj->smb_obj);
    }
    ExceptCatch
    {
        if(ExceptCaught()->errnum != 0x10020)
            ExceptRethrow();
    }
}



fsys_handle_t FsysOpen(fsys_object_t obj, int update, unsigned int fd)
{
    fsys_handle_t h;

    if(PrinterClaimsObject(obj))
        return PrinterOpen(obj, update, fd);
    finish_keepalive(obj->img);
    h = Malloc(sizeof(*h));
    ExceptTry
    {
        h->is_printer = 0;
        h->obj = obj;
        h->fd = fd;
        VCLog("open %s for %s", SMBGetName(obj->smb_obj),
              update ? "update" : "reading");
        h->smb_handle = SMBOpen(obj->smb_obj, update);
        /*
            Everything FileSwitch is about to be told.  It decides from
            these whether the file may be read at all, and in what sized
            pieces; a fault that never reaches the wire is a fault in one
            of these numbers, so log them rather than guess later.
        */
        VCLog("     length %d, handle %d, type %d, attributes &%02X%s%s",
              SMBGetLength(obj->smb_obj), fd, FsysGetType(obj),
              FsysGetAttributes(obj),
              (FsysGetAttributes(obj) & FSYS_READ) ? ", readable" : ", NOT READABLE",
              (FsysGetAttributes(obj) & FSYS_WRITE) ? ", writable" : "");
        VCLog("     block size %d%s", FsysGetBlockSize(obj),
              (FsysGetBlockSize(obj) == 0) ? " (unbuffered)" : "");
        if(fd < 256)
            user_of_file_descriptor[fd] = obj->img;
    }
    ExceptCatch
    {
        VCLogErr("open failed", ExceptCaught());
        Free(h);
        ExceptRethrow();
    }
    return h;
}

void FsysClose(fsys_handle_t h)
{
    if(h->is_printer)
    {
        PrinterClose(h);
        return;
    }
    finish_keepalive(h->obj->img);
    VCLog("close handle %d", h->fd);
    SMBClose(h->smb_handle);
    if(h->fd < 256)
        user_of_file_descriptor[h->fd] = NULL;
    Free(h);
}

int FsysGetSize(fsys_handle_t h)
{
    int b;

    if(h->is_printer)
        return 0;
    finish_keepalive(h->obj->img);
    /* Round up to a whole block.  The + used to bind tighter than the &,
       and the mask was not inverted, so this returned the length modulo
       the block size rather than the length rounded up to it. */
    /*
        "space currently allocated to file (buffered files only: must be a
        multiple of buffer size)" - so this rounds up to whatever was
        actually reported as the buffer size, not to a fixed figure.
    */
    b = FsysGetBlockSize(h->obj);
    if(b <= 0)
        b = 1;                          /* unbuffered: the figure is ignored */
    VCLog("     allocated size asked for: %d (length %d, block %d)",
          (SMBGetLength(h->obj->smb_obj) + (b - 1)) & ~(b - 1),
          SMBGetLength(h->obj->smb_obj), b);
    return (SMBGetLength(h->obj->smb_obj) + (b - 1)) & ~(b - 1);
}

void FsysEnsureSize(fsys_handle_t h, int s)
{
    int s_now;

    if(h->is_printer)
        return;
    finish_keepalive(h->obj->img);
    s_now = SMBGetLength(h->obj->smb_obj);
    if(s_now < s)
    {
        SMBSetExtent(h->smb_handle, s);
        SMBSetLength(h->obj->smb_obj, s);
    }
}

void FsysLimitSize(fsys_handle_t h, int s)
{
    if(h->is_printer)
    {
        PrinterLimitSize(h, s);
        return;
    }
    finish_keepalive(h->obj->img);
    SMBSetExtent(h->smb_handle, s);
}

fsys_object_t FsysHandle2File(fsys_handle_t h)
{
    if(h->is_printer)
        return PrinterHandle2File(h);
    return h->obj;
}

void FsysRead(void *buf, int pos, int count, fsys_handle_t h)
{
    // static int err_id = 0;

    ExceptTry
    {
        if(h->is_printer)
            Error("Can't read from a printer");
        finish_keepalive(h->obj->img);
        VCLog("read %d bytes at %d", count, pos);
        SMBRead(buf, pos, count, h->smb_handle);
    }
    ExceptCatch
    {
        // show_error(&err_id, "Read", ExceptCaught());
        VCLogErr("read failed", ExceptCaught());
        ExceptRethrow();
    }
}

void FsysWrite(void *buf, int pos, int count, fsys_handle_t h)
{
    if(h->is_printer)
    {
        PrinterWrite(buf, pos, count, h);
        return;
    }
    finish_keepalive(h->obj->img);
    VCLog("write %d bytes at %d", count, pos);
    SMBWrite(buf, pos, count, h->smb_handle);
}
