#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <libsec.h>
#include <9p.h>
#include "cifs.h"

#define max(a,b)        (((a) > (b))? (a): (b))
#define min(a,b)        (((a) < (b))? (a): (b))

typedef struct Aux Aux;
struct Aux {
       Aux     *next;
       Aux     *prev;
       char    *path;          /* full path fo file */
       Share   *sp;            /* this share's info */
       long    expire;         /* expiration time of cache */
       long    off;            /* file pos of start of cache */
       long    end;            /* file pos of end of cache */
       long    mtime;          /* last modification time - windows updates it only on close */
       char    *cache;
       int     fh;             /* file handle */
       int     sh;             /* search handle */
       long    srch;           /* find first's internal state */
};

extern int chatty9p;

int Checkcase = 1;              /* enforce case significance on filenames */
int Dfstout = 100;              /* timeout (in ms) for ping of dfs servers (assume they are local)  */
int Billtrog = 1;               /* enable file owner/group resolution */
int Attachpid;                  /* pid of proc that attaches (ugh !) */
char *Debug = nil;              /* messages */
Qid Root;                       /* root of remote system */
Share Ipc;                      /* Share info of IPC$ share */
Session *Sess;                  /* current session */
int Active = IDLE_TIME;         /* secs until next keepalive is sent */
static int Keeppid;             /* process ID of keepalive thread */
Share Shares[MAX_SHARES];       /* table of connected shares */
int Nshares = 0;                /* number of Shares connected */
Aux *Openfiles = nil;           /* linked list of Aux structs */
char *Host = nil;               /* host we are connected to */

static char *Ipcname = "IPC$";

#define ptype(x)        (((x) & 0xf))
#define pindex(x)       (((x) & 0xff0) >> 4)

void
setup(void)
{
       int fd;
       char buf[32];

       /*
        * This is revolting but I cannot see any other way to get
        * the pid of the server.  We need this as Windows doesn't
        * drop the TCP connection when it closes a connection.
        * Thus we keepalive() to detect when/if we are thrown off.
        */
       Attachpid = getpid();

       snprint(buf, sizeof buf, "#p/%d/args", getpid());
       if((fd = open(buf, OWRITE)) >= 0){
               fprint(fd, "%s network", Host);
               close(fd);
       }
}

int
filetableinfo(Fmt *f)
{
       Aux *ap;
       char *type;

       if((ap = Openfiles) != nil)
               do{
                       type = "walked";
                       if(ap->sh != -1)
                               type = "opendir";
                       if(ap->fh != -1)
                               type = "openfile";
                       fmtprint(f, "%-9s %s\n", type, ap->path);
                       ap = ap->next;
               }while(ap != Openfiles);
       return 0;
}

Qid
mkqid(char *s, int is_dir, long vers, int subtype, long path)
{
       Qid q;
       union {                         /* align digest suitably */
               uchar   digest[SHA1dlen];
               uvlong  uvl;
       } u;

       sha1((uchar *)s, strlen(s), u.digest, nil);
       q.type = (is_dir)? QTDIR: 0;
       q.vers = vers;
       if(subtype){
               q.path = *((uvlong *)u.digest) & ~0xfffL;
               q.path |= ((path & 0xff) << 4);
               q.path |= (subtype & 0xf);
       }
       else
               q.path = *((uvlong *)u.digest) & ~0xfL;
       return q;
}

/*
* used only for root dir and shares
*/
static void
V2D(Dir *d, Qid qid, char *name)
{
       memset(d, 0, sizeof(Dir));
       d->type = 'C';
       d->dev = 1;
       d->name = estrdup9p(name);
       d->uid = estrdup9p("bill");
       d->muid = estrdup9p("boyd");
       d->gid = estrdup9p("trog");
       d->mode = 0755 | DMDIR;
       d->atime = time(nil);
       d->mtime = d->atime;
       d->length = 0;
       d->qid = qid;
}

static void
I2D(Dir *d, Share *sp, char *path, long mtime, FInfo *fi)
{
       char *name;

       if((name = strrchr(fi->name, '\\')) != nil)
               name++;
       else
               name = fi->name;
       d->name = estrdup9p(name);
       d->type = 'C';
       d->dev = sp->tid;
       d->uid = estrdup9p("bill");
       d->gid = estrdup9p("trog");
       d->muid = estrdup9p("boyd");
       d->atime = fi->accessed;
       if(mtime > fi->written)
               d->mtime = mtime;
       else
               d->mtime = fi->written;

       if(fi->attribs & ATTR_READONLY)
               d->mode = 0444;
       else
               d->mode = 0666;

       d->length = fi->size;
       d->qid = mkqid(path, fi->attribs & ATTR_DIRECTORY, fi->changed, 0, 0);

       if(fi->attribs & ATTR_DIRECTORY){
               d->length = 0;
               d->mode |= DMDIR|0111;
       }
}

static void
responderrstr(Req *r)
{
       char e[ERRMAX];

       *e = 0;
       rerrstr(e, sizeof e);
       respond(r, e);
}

static char *
newpath(char *path, char *name)
{
       char *p, *q;

       assert((p = strrchr(path, '/')) != nil);

       if(strcmp(name, "..") == 0){
               if(p == path)
                       return estrdup9p("/");
               q = emalloc9p((p-path)+1);
               strecpy(q, q+(p-path)+1, path);
               return q;
       }
       if(strcmp(path, "/") == 0)
               return smprint("/%s", name);
       return smprint("%s/%s", path, name);
}

/*
* get the last write time if the file is open -
* windows only updates mtime when the file is closed
* which is not good enough for acme.
*/
static long
realmtime(char *path)
{
       Aux *a;

       if((a = Openfiles) == nil)
               return 0;

       do{
               if(a->fh != -1 && cistrcmp(path, a->path) == 0)
                       return a->mtime;
               a = a->next;
       }while(a != Openfiles);
       return 0;
}

/* remove "." and ".." from the cache */
static int
rmdots(Aux *a, int got)
{
       int i, num;
       FInfo *fi;

       num = 0;
       fi = (FInfo *)a->cache;
       for(i = 0; i < got; i++){
               if(strcmp(fi->name, ".") == 0 || strcmp(fi->name, "..") == 0){
                       memmove(fi, fi+1, got * sizeof(FInfo));
                       continue;
               }
               fi++;
               num++;
       }

       return num;
}

static int
dirgen(int slot, Dir *d, void *aux)
{
       long off;
       FInfo *fi;
       int rc, got;
       Aux *a = aux;
       char *npath;
       int numinf = numinfo();
       int slots;

       slots = 32;             /* number of dir entries to fetch at one time */

       if(strcmp(a->path, "/") == 0){
               if(slot < numinf){
                       dirgeninfo(slot, d);
                       return 0;
               } else
                       slot -= numinf;

               if(slot >= Nshares)
                       return -1;
               V2D(d, mkqid(Shares[slot].name, 1, 1, Pshare, slot),
                       Shares[slot].name);
               return 0;
       }

       off = slot * sizeof(FInfo);
       if(off >= a->off && off < a->end && time(nil) < a->expire)
               goto from_cache;

       if(off == 0){
               npath = smprint("%s/*", mapfile(a->path));
               a->sh = T2findfirst(Sess, a->sp, slots, npath, &got, &a->srch,
                       (FInfo *)a->cache);
               free(npath);
               if(a->sh == -1)
                       return -1;

               got = rmdots(a, got);
               a->off = 0;
               a->end = got * sizeof(FInfo);
               goto from_cache;
       }

       while(off >= a->end && a->sh != -1){
               fi = (FInfo *)(a->cache + (a->end - a->off) - sizeof(FInfo));
               a->off = a->end;
               npath = smprint("%s/%s", mapfile(a->path), fi->name);
               rc = T2findnext(Sess, a->sp, slots, npath,
                       &got, &a->srch, (FInfo *)a->cache, a->sh);
               free(npath);
               if(rc == -1 || got == 0)
                       break;
               got = rmdots(a, got);
               a->end = a->off + got * sizeof(FInfo);
       }
       a->expire = time(nil) + CACHETIME;

       if(got < slots){
               if(a->sh != -1)
                       CIFSfindclose2(Sess, a->sp, a->sh);
               a->sh = -1;
       }


from_cache:
       if(off >= a->end)
               return -1;

       fi = (FInfo *)(a->cache + (off - a->off));
       npath = smprint("%s/%s", mapfile(a->path), fi->name);
       I2D(d, a->sp, npath, realmtime(npath), fi);
       if(Billtrog == 0)
               upd_names(Sess, a->sp, npath, d);
       free(npath);
       return 0;
}

static void
fsattach(Req *r)
{
       Aux *a;
       static int first = 1;
       char *spec = r->ifcall.aname;

       if(first)
               setup();

       if(spec && *spec){
               respond(r, "invalid attach specifier");
               return;
       }

       r->ofcall.qid = mkqid("/", 1, 1, Proot, 0);
       r->fid->qid = r->ofcall.qid;

       a = r->fid->aux = emalloc9p(sizeof(Aux));
       memset(a, 0, sizeof(Aux));
       a->path = estrdup9p("/");
       a->sp = nil;
       a->fh = -1;
       a->sh = -1;

       if(Openfiles){
               a->prev = Openfiles;
               a->next = Openfiles->next;
               Openfiles->next->prev = a;
               Openfiles->next = a;
       } else {
               Openfiles = a;
               a->next = a;
               a->prev = a;
       }
       respond(r, nil);
}

static char*
fsclone(Fid *ofid, Fid *fid)
{
       Aux *oa = ofid->aux;
       Aux *a = emalloc9p(sizeof(Aux));

       fid->aux = a;

       memset(a, 0, sizeof(Aux));
       a->sh = -1;
       a->fh = -1;
       a->sp = oa->sp;
       a->path = estrdup9p(oa->path);

       if(Openfiles){
               a->prev = Openfiles;
               a->next = Openfiles->next;
               Openfiles->next->prev = a;
               Openfiles->next = a;
       } else {
               Openfiles = a;
               a->next = a;
               a->prev = a;
       }
       return nil;
}

/*
* for some weird reason T2queryall() returns share names
* in lower case so we have to do an extra test against
* our share table to validate filename case.
*
* on top of this here (snell & Wilcox) most of our
* redirections point to a share of the same name,
* but some do not, thus the tail of the filename
* returned by T2queryall() is not the same as
* the name we wanted.
*
* We work around this by not validating the names
* or files which resolve to share names as they must
* be correct, having been enforced in the dfs layer.
*/
static int
validfile(char *found, char *want, char *winpath, Share *sp)
{
       char *share;

       if(strcmp(want, "..") == 0)
               return 1;
       if(strcmp(winpath, "/") == 0){
               share = trimshare(sp->name);
               if(cistrcmp(want, share) == 0)
                       return strcmp(want, share) == 0;
               /*
                * OK, a DFS redirection points us from a directory XXX
                * to a share named YYY.  There is no case checking we can
                * do so we allow either case - it's all we can do.
                */
               return 1;
       }
       if(cistrcmp(found, want) != 0)
               return 0;
       if(!Checkcase)
               return 1;
       if(strcmp(found, want) == 0)
               return 1;
       return 0;
}


static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
       FInfo fi;
       int rc, n, i;
       Aux *a = fid->aux;
       static char e[ERRMAX];
       char *p, *npath, *winpath;

       *e = 0;
       npath = newpath(a->path, name);
       if(strcmp(npath, "/") == 0){                    /* root dir */
               *qid = mkqid("/", 1, 1, Proot, 0);
               free(a->path);
               a->path = npath;
               fid->qid = *qid;
               return nil;
       }

       if(strrchr(npath, '/') == npath){               /* top level dir */
               if((n = walkinfo(name)) != -1){         /* info file */
                       *qid = mkqid(npath, 0, 1, Pinfo, n);
               }
               else {                                  /* volume name */
                       for(i = 0; i < Nshares; i++){
                               n = strlen(Shares[i].name);
                               if(cistrncmp(npath+1, Shares[i].name, n) != 0)
                                       continue;
                               if(Checkcase && strncmp(npath+1, Shares[i].name, n) != 0)
                                       continue;
                               if(npath[n+1] != 0 && npath[n+1] != '/')
                                       continue;
                               break;
                       }
                       if(i >= Nshares){
                               free(npath);
                               return "not found";
                       }
                       a->sp = Shares+i;
                       *qid = mkqid(npath, 1, 1, Pshare, i);
               }
               free(a->path);
               a->path = npath;
               fid->qid = *qid;
               return nil;
       }

       /* must be a vanilla file or directory */
again:
       if(mapshare(npath, &a->sp) == -1){
               rerrstr(e, sizeof(e));
               free(npath);
               return e;
       }

       winpath = mapfile(npath);
       memset(&fi, 0, sizeof fi);
       if(Sess->caps & CAP_NT_SMBS)
               rc = T2queryall(Sess, a->sp, winpath, &fi);
       else
               rc = T2querystandard(Sess, a->sp, winpath, &fi);

       if(rc == -1){
               rerrstr(e, sizeof(e));
               free(npath);
               return e;
       }

       if((a->sp->options & SMB_SHARE_IS_IN_DFS) != 0 &&
           (fi.attribs & ATTR_REPARSE) != 0){
               if(redirect(Sess, a->sp, npath) != -1)
                       goto again;
       }

       if((p = strrchr(fi.name, '/')) == nil && (p = strrchr(fi.name, '\\')) == nil)
               p = fi.name;
       else
               p++;

       if(! validfile(p, name, winpath, a->sp)){
               free(npath);
               return "not found";

       }
       *qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);

       a->mtime = realmtime(npath);

       free(a->path);
       a->path = npath;
       fid->qid = *qid;
       return nil;
}

static void
fsstat(Req *r)
{
       int rc;
       FInfo fi;
       Aux *a = r->fid->aux;

       if(ptype(r->fid->qid.path) == Proot)
               V2D(&r->d, r->fid->qid, "");
       else if(ptype(r->fid->qid.path) == Pinfo)
               dirgeninfo(pindex(r->fid->qid.path), &r->d);
       else if(ptype(r->fid->qid.path) == Pshare)
               V2D(&r->d, r->fid->qid, a->path +1);
       else{
               memset(&fi, 0, sizeof fi);
               if(Sess->caps & CAP_NT_SMBS)
                       rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
               else
                       rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
               if(rc == -1){
                       responderrstr(r);
                       return;
               }
               I2D(&r->d, a->sp, a->path, a->mtime, &fi);
               if(Billtrog == 0)
                       upd_names(Sess, a->sp, mapfile(a->path), &r->d);
       }
       respond(r, nil);
}

static int
smbcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
       int is_dir, FInfo *fip)
{
       int rc, action, attrs, access, result;

       if(is_create && is_dir){
               if(CIFScreatedirectory(Sess, a->sp, path) == -1)
                       return -1;
               return 0;
       }

       if(mode & DMAPPEND) {
               werrstr("filesystem does not support DMAPPEND");
               return -1;
       }

       if(is_create)
               action = 0x12;
       else if(mode & OTRUNC)
               action = 0x02;
       else
               action = 0x01;

       if(perm & 0222)
               attrs = ATTR_NORMAL;
       else
               attrs = ATTR_NORMAL|ATTR_READONLY;

       switch (mode & OMASK){
       case OREAD:
               access = 0;
               break;
       case OWRITE:
               access = 1;
               break;
       case ORDWR:
               access = 2;
               break;
       case OEXEC:
               access = 3;
               break;
       default:
               werrstr("%d bad open mode", mode & OMASK);
               return -1;
               break;
       }

       if((mode & DMEXCL) == 0)
               access |= 0x10;
       else
               access |= 0x40;

       if((a->fh = CIFS_SMB_opencreate(Sess, a->sp, path, access, attrs,
           action, &result)) == -1)
               return -1;

       if(Sess->caps & CAP_NT_SMBS)
               rc = T2queryall(Sess, a->sp, mapfile(a->path), fip);
       else
               rc = T2querystandard(Sess, a->sp, mapfile(a->path), fip);
       if(rc == -1){
               fprint(2, "internal error: stat of newly open/created file failed\n");
               return -1;
       }

       if((mode & OEXCL) && (result & 0x8000) == 0){
               werrstr("%d bad open mode", mode & OMASK);
               return -1;
       }
       return 0;
}

/* Uncle Bill, you have a lot to answer for... */
static int
ntcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
       int is_dir, FInfo *fip)
{
       int options, result, attrs, flags, access, action, share;

       if(mode & DMAPPEND){
               werrstr("CIFSopen, DMAPPEND not supported");
               return -1;
       }

       if(is_create){
               if(mode & OEXCL)
                       action = FILE_OPEN;
               else if(mode & OTRUNC)
                       action = FILE_CREATE;
               else
                       action = FILE_OVERWRITE_IF;
       } else {
               if(mode & OTRUNC)
                       action = FILE_OVERWRITE_IF;
               else
                       action = FILE_OPEN_IF;
       }

       flags = 0;              /* FIXME: really not sure */

       if(mode & OEXCL)
               share = FILE_NO_SHARE;
       else
               share = FILE_SHARE_ALL;

       switch (mode & OMASK){
       case OREAD:
               access = GENERIC_READ;
               break;
       case OWRITE:
               access = GENERIC_WRITE;
               break;
       case ORDWR:
               access = GENERIC_ALL;
               break;
       case OEXEC:
               access = GENERIC_EXECUTE;
               break;
       default:
               werrstr("%d bad open mode", mode & OMASK);
               return -1;
               break;
       }

       if(is_dir){
               action = FILE_CREATE;
               options = FILE_DIRECTORY_FILE;
               if(perm & 0222)
                       attrs = ATTR_DIRECTORY;
               else
                       attrs = ATTR_DIRECTORY|ATTR_READONLY;
       } else {
               options = FILE_NON_DIRECTORY_FILE;
               if(perm & 0222)
                       attrs = ATTR_NORMAL;
               else
                       attrs = ATTR_NORMAL|ATTR_READONLY;
       }

       if(mode & ORCLOSE){
               options |= FILE_DELETE_ON_CLOSE;
               attrs |= ATTR_DELETE_ON_CLOSE;
       }

       if((a->fh = CIFS_NT_opencreate(Sess, a->sp, path, flags, options,
           attrs, access, share, action, &result, fip)) == -1)
               return -1;

       if((mode & OEXCL) && (result & 0x8000) == 0){
               werrstr("%d bad open mode", mode & OMASK);
               return -1;
       }

       return 0;
}

static void
fscreate(Req *r)
{
       FInfo fi;
       int rc, is_dir;
       char *npath;
       Aux *a = r->fid->aux;

       a->end = a->off = 0;
       a->cache = emalloc9p(max(Sess->mtu, MTU));

       is_dir = (r->ifcall.perm & DMDIR) == DMDIR;
       npath = smprint("%s/%s", a->path, r->ifcall.name);

       if(Sess->caps & CAP_NT_SMBS)
               rc = ntcreateopen(a, mapfile(npath), r->ifcall.mode,
                       r->ifcall.perm, 1, is_dir, &fi);
       else
               rc = smbcreateopen(a, mapfile(npath), r->ifcall.mode,
                       r->ifcall.perm, 1, is_dir, &fi);
       if(rc == -1){
               free(npath);
               responderrstr(r);
               return;
       }

       r->fid->qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);

       r->ofcall.qid = r->fid->qid;
       free(a->path);
       a->path = npath;

       respond(r, nil);
}

static void
fsopen(Req *r)
{
       int rc;
       FInfo fi;
       Aux *a = r->fid->aux;

       a->end = a->off = 0;
       a->cache = emalloc9p(max(Sess->mtu, MTU));

       if(ptype(r->fid->qid.path) == Pinfo){
               if(makeinfo(pindex(r->fid->qid.path)) != -1)
                       respond(r, nil);
               else
                       respond(r, "cannot generate info");
               return;
       }

       if(r->fid->qid.type & QTDIR){
               respond(r, nil);
               return;
       }

       if(Sess->caps & CAP_NT_SMBS)
               rc = ntcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
                       0, 0, &fi);
       else
               rc = smbcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
                       0, 0, &fi);
       if(rc == -1){
               responderrstr(r);
               return;
       }
       respond(r, nil);
}

static void
fswrite(Req *r)
{
       vlong n, m, got;
       Aux *a = r->fid->aux;
       vlong len = r->ifcall.count;
       vlong off = r->ifcall.offset;
       char *buf = r->ifcall.data;

       got = 0;
       n = Sess->mtu -OVERHEAD;
       do{
               if(len - got < n)
                       n = len - got;
               m = CIFSwrite(Sess, a->sp, a->fh, off + got, buf + got, n);
               if(m != -1)
                       got += m;
       } while(got < len && m >= n);

       r->ofcall.count = got;
       a->mtime = time(nil);

       if(m == -1)
               responderrstr(r);
       else
               respond(r, nil);
}

static void
fsread(Req *r)
{
       vlong n, m, got;
       Aux *a = r->fid->aux;
       char *buf = r->ofcall.data;
       vlong len = r->ifcall.count;
       vlong off = r->ifcall.offset;

       if(ptype(r->fid->qid.path) == Pinfo){
               r->ofcall.count = readinfo(pindex(r->fid->qid.path), buf, len,
                       off);
               respond(r, nil);
               return;
       }

       if(r->fid->qid.type & QTDIR){
               dirread9p(r, dirgen, a);
               respond(r, nil);
               return;
       }

       got = 0;
       n = Sess->mtu -OVERHEAD;
       do{
               if(len - got < n)
                       n = len - got;
               m = CIFSread(Sess, a->sp, a->fh, off + got, buf + got, n, len);
               if(m != -1)
                       got += m;
       } while(got < len && m >= n);

       r->ofcall.count = got;
       if(m == -1)
               responderrstr(r);
       else
               respond(r, nil);
}

static void
fsdestroyfid(Fid *f)
{
       Aux *a = f->aux;

       if(ptype(f->qid.path) == Pinfo)
               freeinfo(pindex(f->qid.path));
       f->omode = -1;
       if(! a)
               return;
       if(a->fh != -1)
               if(CIFSclose(Sess, a->sp, a->fh) == -1)
                       fprint(2, "%s: close failed fh=%d %r\n", argv0, a->fh);
       if(a->sh != -1)
               if(CIFSfindclose2(Sess, a->sp, a->sh) == -1)
                       fprint(2, "%s: findclose failed sh=%d %r\n",
                               argv0, a->sh);
       if(a->path)
               free(a->path);
       if(a->cache)
               free(a->cache);

       if(a == Openfiles)
               Openfiles = a->next;
       a->prev->next = a->next;
       a->next->prev = a->prev;
       if(a->next == a->prev)
               Openfiles = nil;
       if(a)
               free(a);
}

int
rdonly(Session *s, Share *sp, char *path, int rdonly)
{
       int rc;
       FInfo fi;

       if(Sess->caps & CAP_NT_SMBS)
               rc = T2queryall(s, sp, path, &fi);
       else
               rc = T2querystandard(s, sp, path, &fi);
       if(rc == -1)
               return -1;

       if((rdonly && !(fi.attribs & ATTR_READONLY)) ||
           (!rdonly && (fi.attribs & ATTR_READONLY))){
               fi.attribs &= ~ATTR_READONLY;
               fi.attribs |= rdonly? ATTR_READONLY: 0;
               rc = CIFSsetinfo(s, sp, path, &fi);
       }
       return rc;
}

static void
fsremove(Req *r)
{
       int try, rc;
       char e[ERRMAX];
       Aux *ap, *a = r->fid->aux;

       *e = 0;
       if(ptype(r->fid->qid.path) == Proot ||
          ptype(r->fid->qid.path) == Pshare){
               respond(r, "illegal operation");
               return;
       }

       /* close all instences of this file/dir */
       if((ap = Openfiles) != nil)
               do{
                       if(strcmp(ap->path, a->path) == 0){
                               if(ap->sh != -1)
                                       CIFSfindclose2(Sess, ap->sp, ap->sh);
                               ap->sh = -1;
                               if(ap->fh != -1)
                                       CIFSclose(Sess, ap->sp, ap->fh);
                               ap->fh = -1;
                       }
                       ap = ap->next;
               }while(ap != Openfiles);
       try = 0;
again:
       if(r->fid->qid.type & QTDIR)
               rc = CIFSdeletedirectory(Sess, a->sp, mapfile(a->path));
       else
               rc = CIFSdeletefile(Sess, a->sp, mapfile(a->path));

       rerrstr(e, sizeof(e));
       if(rc == -1 && try++ == 0 && strcmp(e, "permission denied") == 0 &&
           rdonly(Sess, a->sp, mapfile(a->path), 0) == 0)
               goto again;
       if(rc == -1)
               responderrstr(r);
       else
               respond(r, nil);
}

static void
fswstat(Req *r)
{
       int fh, result, rc;
       FInfo fi, tmpfi;
       char *p, *from, *npath;
       Aux *a = r->fid->aux;

       if(ptype(r->fid->qid.path) == Proot ||
          ptype(r->fid->qid.path) == Pshare){
               respond(r, "illegal operation");
               return;
       }

       if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){
               respond(r, "cannot change ownership");
               return;
       }

       /*
        * get current info
        */
       if(Sess->caps & CAP_NT_SMBS)
               rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
       else
               rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
       if(rc == -1){
               werrstr("(query) - %r");
               responderrstr(r);
               return;
       }

       /*
        * always clear the readonly attribute if set,
        * before trying to set any other fields.
        * wstat() fails if the file/dir is readonly
        * and this function is so full of races - who cares about one more?
        */
       rdonly(Sess, a->sp, mapfile(a->path), 0);

       /*
        * rename - one piece of joy, renaming open files
        * is legal (sharing permitting).
        */
       if(r->d.name && r->d.name[0]){
               if((p = strrchr(a->path, '/')) == nil){
                       respond(r, "illegal path");
                       return;
               }
               npath = emalloc9p((p-a->path)+strlen(r->d.name)+2);
               strecpy(npath, npath+(p- a->path)+2, a->path);
               strcat(npath, r->d.name);

               from = estrdup9p(mapfile(a->path));
               if(CIFSrename(Sess, a->sp, from, mapfile(npath)) == -1){
                       werrstr("(rename) - %r");
                       responderrstr(r);
                       free(npath);
                       free(from);
                       return;
               }
               free(from);
               free(a->path);
               a->path = npath;
       }

       /*
        * set the files length, do this before setting
        * the file times as open() will alter them
        */
       if(~r->d.length){
               fi.size = r->d.length;

               if(Sess->caps & CAP_NT_SMBS){
                       if((fh = CIFS_NT_opencreate(Sess, a->sp, mapfile(a->path),
                           0, FILE_NON_DIRECTORY_FILE,
                           ATTR_NORMAL, GENERIC_WRITE, FILE_SHARE_ALL,
                           FILE_OPEN_IF, &result, &tmpfi)) == -1){
                               werrstr("(set length, open) - %r");
                               responderrstr(r);
                               return;
                       }
                       rc = T2setfilelength(Sess, a->sp, fh, &fi);
                       CIFSclose(Sess, a->sp, fh);
                       if(rc == -1){
                               werrstr("(set length), set) - %r");
                               responderrstr(r);
                               return;
                       }
               } else {
                       if((fh = CIFS_SMB_opencreate(Sess, a->sp, mapfile(a->path),
                           1, ATTR_NORMAL, 1, &result)) == -1){
                               werrstr("(set length, open) failed - %r");
                               responderrstr(r);
                               return;
                       }
                       rc = CIFSwrite(Sess, a->sp, fh, fi.size, 0, 0);
                       CIFSclose(Sess, a->sp, fh);
                       if(rc == -1){
                               werrstr("(set length, write) - %r");
                               responderrstr(r);
                               return;
                       }
               }
       }

       /*
        * This doesn't appear to set length or
        * attributes, no idea why, so I do those seperately
        */
       if(~r->d.mtime || ~r->d.atime){
               if(~r->d.mtime)
                       fi.written = r->d.mtime;
               if(~r->d.atime)
                       fi.accessed = r->d.atime;
               if(T2setpathinfo(Sess, a->sp, mapfile(a->path), &fi) == -1){
                       werrstr("(set path info) - %r");
                       responderrstr(r);
                       return;
               }
       }

       /*
        * always update the readonly flag as
        * we may have cleared it above.
        */
       if(~r->d.mode){
               if(r->d.mode & 0222)
                       fi.attribs &= ~ATTR_READONLY;
               else
                       fi.attribs |= ATTR_READONLY;
       }
       if(rdonly(Sess, a->sp, mapfile(a->path), fi.attribs & ATTR_READONLY) == -1){
               werrstr("(set info) - %r");
               responderrstr(r);
               return;
       }

       /*
        * Win95 has a broken write-behind cache for metadata
        * on open files (writes go to the cache, reads bypass
        * the cache), so we must flush the file.
        */
       if(r->fid->omode != -1 && CIFSflush(Sess, a->sp, a->fh) == -1){
               werrstr("(flush) %r");
               responderrstr(r);
               return;
       }
       respond(r, nil);
}

static void
fsend(Srv *srv)
{
       int i;
       USED(srv);

       for(i = 0; i < Nshares; i++)
               CIFStreedisconnect(Sess, Shares+i);
       CIFSlogoff(Sess);
       postnote(PNPROC, Keeppid, "die");
}

Srv fs = {
       .destroyfid =   fsdestroyfid,
       .attach=        fsattach,
       .open=          fsopen,
       .create=        fscreate,
       .read=          fsread,
       .write=         fswrite,
       .remove=        fsremove,
       .stat=          fsstat,
       .wstat=         fswstat,
       .clone=         fsclone,
       .walk1=         fswalk1,
       .end=           fsend,
};

void
usage(void)
{
       fprint(2, "usage: %s [-d name] [-Dvb] [-a auth-method] [-s srvname] "
               "[-n called-name] [-k factotum-params] [-m mntpnt] "
               "host [share...]\n", argv0);
       exits("usage");
}

/*
* SMBecho looks like the function to use for keepalives,
* sadly the echo packet does not seem to reload the
* idle timer in Microsoft's servers.  Instead we use
* "get file system size" on each share until we get one that succeeds.
*/
static void
keepalive(void)
{
       char buf[32];
       uvlong tot, fre;
       int fd, i, slot, rc;

       snprint(buf, sizeof buf, "#p/%d/args", getpid());
       if((fd = open(buf, OWRITE)) >= 0){
               fprint(fd, "%s keepalive", Host);
               close(fd);
       }

       rc = 0;
       slot = 0;
       do{
               sleep(6000);
               if(Active-- != 0)
                       continue;

               for(i = 0; i < Nshares; i++){
                       if((rc = T2fssizeinfo(Sess, &Shares[slot], &tot, &fre)) == 0)
                               break;
                       if(++slot >= Nshares)
                               slot = 0;
               }
       }while(rc != -1);
       postnote(PNPROC, Attachpid, "die");
}


static void
ding(void *u, char *msg)
{
       USED(u);
       if(strstr(msg, "alarm") != nil)
               noted(NCONT);
       noted(NDFLT);
}

void
dmpkey(char *s, void *v, int n)
{
       int i;
       unsigned char *p = (unsigned char *)v;

       print("%s", s);
       for(i = 0; i < n; i++)
               print("%02ux ", *p++);
       print("\n");
}

void
main(int argc, char **argv)
{
       int i, n, local;
       long svrtime;
       char windom[64], cname[64];
       char *p, *method, *sysname, *keyp, *mtpt, *svs;
       static char *sh[1024];

       local = 0;
       *cname = 0;
       keyp = "";
       method = nil;
       strcpy(windom, "unknown");
       mtpt = svs = nil;

       notify(ding);

       ARGBEGIN{
       case 'a':
               method = EARGF(autherr());
               break;
       case 'b':
               Billtrog ^= 1;
               break;
       case 'D':
               chatty9p++;
               break;
       case 'd':
               Debug = EARGF(usage());
               break;
       case 'i':
               Checkcase = 0;
               break;
       case 'k':
               keyp = EARGF(usage());
               break;
       case 'l':
               local++;
               break;
       case 'm':
               mtpt = EARGF(usage());
               break;
       case 'n':
               strncpy(cname, EARGF(usage()), sizeof(cname));
               cname[sizeof(cname) - 1] = 0;
               break;
       case 's':
               svs = EARGF(usage());
               break;
       case 't':
               Dfstout = atoi(EARGF(usage()));
               break;
       default:
               usage();
               break;
       }ARGEND

       if(argc < 1)
               usage();

       Host = argv[0];

       if(mtpt == nil && svs == nil){
               if((p = strchr(Host, '!')) != nil)
                       mtpt = smprint("/n/%s", p+1);
               else
                       mtpt = smprint("/n/%s", Host);
       }

       if((sysname = getenv("sysname")) == nil)
               sysname = "unknown";

       if(*cname && (Sess = cifsdial(Host, cname, sysname)) != nil)
               goto connected;

       if(calledname(Host, cname) == 0 &&
           (Sess = cifsdial(Host, cname, sysname)) != nil)
               goto connected;

       strcpy(cname, Host);
       if((p = strchr(cname, '!')) != nil)
               strcpy(cname, p+1);

       if((Sess = cifsdial(Host, cname, sysname)) != nil ||
          (Sess = cifsdial(Host, "*SMBSERVER", sysname)) != nil)
               goto connected;

       sysfatal("%s - cannot dial, %r\n", Host);
connected:
       if(CIFSnegotiate(Sess, &svrtime, windom, sizeof windom, cname, sizeof cname) == -1)
               sysfatal("%s - cannot negioate common protocol, %r\n", Host);

#ifndef DEBUG_MAC
       Sess->secmode &= ~SECMODE_SIGN_ENABLED;
#endif

       if(local)
               strcpy(windom, ".");

       Sess->auth = getauth(method, windom, keyp, Sess->secmode, Sess->chal,
               Sess->challen);

       if(CIFSsession(Sess) < 0)
               sysfatal("session authentication failed, %r\n");

       Sess->slip = svrtime - time(nil);
       Sess->cname = estrdup9p(cname);

       if(CIFStreeconnect(Sess, cname, Ipcname, &Ipc) == -1)
               fprint(2, "%s, %r - can't connect\n", Ipcname);

       Nshares = 0;
       if(argc == 1){
               Share *sip;

               if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1)
                       sysfatal("can't enumerate shares: %r - specify share "
                               "names on command line\n");

               for(i = 0; i < n; i++){
#ifdef NO_HIDDEN_SHARES
                       int l = strlen(sip[i].name);

                       if(l > 1 && sip[i].name[l-1] == '$'){
                               free(sip[i].name);
                               continue;
                       }
#endif
                       memcpy(Shares+Nshares, sip+i, sizeof(Share));
                       if(CIFStreeconnect(Sess, Sess->cname,
                           Shares[Nshares].name, Shares+Nshares) == -1){
                               fprint(2, "%s: %s %q - can't connect to share"
                                       ", %r\n", argv0, Host, Shares[Nshares].name);
                               free(Shares[Nshares].name);
                               continue;
                       }
                       Nshares++;
               }
               free(sip);
       } else
               for(i = 1; i < argc; i++){
                       if(CIFStreeconnect(Sess, Sess->cname, argv[i],
                           Shares+Nshares) == -1){
                               fprint(2, "%s: %s %q - can't connect to share"
                                       ", %r\n", argv0, Host, argv[i]);
                               continue;
                       }
                       Shares[Nshares].name = strlwr(estrdup9p(argv[i]));
                       Nshares++;
               }

       if(Nshares == 0)
               fprint(2, "no available shares\n");

       if((Keeppid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFNAMEG)) == 0){
               keepalive();
               exits(nil);
       }
       postmountsrv(&fs, svs, mtpt, MREPL|MCREATE);
       exits(nil);
}