/*
* cached-worm device
*/
#include "all.h"

#define CDEV(d)         ((d)->cw.c)
#define WDEV(d)         ((d)->cw.w)
#define RDEV(d)         ((d)->cw.ro)

enum {
       FIRST           = SUPER_ADDR,

       ADDFREE         = 100,
       CACHE_ADDR      = SUPER_ADDR,
       MAXAGE          = 10000,
};

/* cache state */
enum
{
       /* states -- beware these are recorded on the cache */
                               /*    cache    worm     */
       Cnone = 0,              /*      0       ?       */
       Cdirty,                 /*      1       0       */
       Cdump,                  /*      1       0->1    */
       Cread,                  /*      1       1       */
       Cwrite,                 /*      2       1       */
       Cdump1,                 /* inactive form of dump */
       Cerror,

       /* opcodes -- these are not recorded */
       Onone,
       Oread,
       Owrite,
       Ogrow,
       Odump,
       Orele,
       Ofree,
};

typedef struct  Cw      Cw;
struct  Cw
{
       Device* dev;
       Device* cdev;
       Device* wdev;
       Device* rodev;
       Cw*     link;

       int     dbucket;        /* last bucket dumped */
       Off     daddr;          /* last block dumped */
       Off     ncopy;
       int     nodump;
/*
* following are cached variables for dumps
*/
       Off     fsize;
       Off     wsize;
       Off     ndump;
       int     depth;
       int     all;            /* local flag to recur on modified dirs */
       int     allflag;        /* global flag to recur on modified dirs */
       Off     falsehits;      /* times recur found modified blocks */
       struct {
               char    name[500];
               char    namepad[NAMELEN+10];
       };
};

static char* cwnames[] =
{
       [Cnone]         "none",
       [Cdirty]        "dirty",
       [Cdump]         "dump",
       [Cread]         "read",
       [Cwrite]        "write",
       [Cdump1]        "dump1",
       [Cerror]        "error",

       [Onone]         "none",
       [Oread]         "read",
       [Owrite]        "write",
       [Ogrow]         "grow",
       [Odump]         "dump",
       [Orele]         "rele",
};

Centry* getcentry(Bucket*, Off);
int     cwio(Device*, Off, void*, int);
void    cmd_cwcmd(int, char*[]);

/*
* console command
* initiate a dump
*/
void
cmd_dump(int argc, char *argv[])
{
       Filsys *fs;

       fs = cons.curfs;
       if(argc > 1)
               fs = fsstr(argv[1]);
       if(fs == 0) {
               print("%s: unknown file system\n", argv[1]);
               return;
       }
       cfsdump(fs);
}

/*
* console command
* worm stats
*/
static void
cmd_statw(int, char*[])
{
       Filsys *fs;
       Iobuf *p;
       Superb *sb;
       Cache *h;
       Bucket *b;
       Centry *c, *ce;
       Off m, nw, bw, state[Onone];
       Off sbfsize, sbcwraddr, sbroraddr, sblast, sbnext;
       Off hmsize, hmaddr, dsize, dsizepct;
       Device *dev;
       Cw *cw;
       int s;

       fs = cons.curfs;
       dev = fs->dev;
       if(dev->type != Devcw) {
               print("curfs not type cw\n");
               return;
       }

       cw = dev->private;
       if(cw == 0) {
               print("curfs not inited\n");
               return;
       }

       print("cwstats %s\n", fs->name);

       sbfsize = 0;
       sbcwraddr = 0;
       sbroraddr = 0;
       sblast = 0;
       sbnext = 0;

       print("\tfilesys %s\n", fs->name);
//      print("\tnio   =%7W%7W%7W\n", cw->ncwio+0, cw->ncwio+1, cw->ncwio+2);
       p = getbuf(dev, cwsaddr(dev), Brd);
       if(!p || checktag(p, Tsuper, QPSUPER)) {
               print("cwstats: checktag super\n");
               if(p) {
                       putbuf(p);
                       p = 0;
               }
       }
       if(p) {
               sb = (Superb*)p->iobuf;
               sbfsize = sb->fsize;
               sbcwraddr = sb->cwraddr;
               sbroraddr = sb->roraddr;
               sblast = sb->last;
               sbnext = sb->next;
               putbuf(p);
       }

       p = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
       if(!p || checktag(p, Tcache, QPSUPER)) {
               print("cwstats: checktag super\n");
               if(p)
                       putbuf(p);
               return;
       }
       h = (Cache*)p->iobuf;
       hmaddr = h->maddr;
       hmsize = h->msize;

       print("\t\tmaddr  = %8lld\n", (Wideoff)hmaddr);
       print("\t\tmsize  = %8lld\n", (Wideoff)hmsize);
       print("\t\tcaddr  = %8lld\n", (Wideoff)h->caddr);
       print("\t\tcsize  = %8lld\n", (Wideoff)h->csize);
       print("\t\tsbaddr = %8lld\n", (Wideoff)h->sbaddr);
       print("\t\tcraddr = %8lld %8lld\n",
               (Wideoff)h->cwraddr, (Wideoff)sbcwraddr);
       print("\t\troaddr = %8lld %8lld\n",
               (Wideoff)h->roraddr, (Wideoff)sbroraddr);
       /* print stats in terms of (first-)disc sides */
       dsize = wormsizeside(dev, 0);
       if (dsize < 1) {
               dsize = h->wsize;       /* it's probably a fake worm */
               if (dsize < 1)
                       dsize = 1000;   /* don't divide by zero */
       }
       dsizepct = dsize/100;
       print("\t\tfsize  = %8lld %8lld %2lld+%2lld%%\n", (Wideoff)h->fsize,
               (Wideoff)sbfsize, (Wideoff)h->fsize/dsize,
               (Wideoff)(h->fsize%dsize)/dsizepct);
       print("\t\tslast  =          %8lld\n", (Wideoff)sblast);
       print("\t\tsnext  =          %8lld\n", (Wideoff)sbnext);
       print("\t\twmax   = %8lld          %2lld+%2lld%%\n",
               (Wideoff)h->wmax, (Wideoff)h->wmax/dsize,
               (Wideoff)(h->wmax%dsize)/dsizepct);
       print("\t\twsize  = %8lld          %2lld+%2lld%%\n",
               (Wideoff)h->wsize, (Wideoff)h->wsize/dsize,
               (Wideoff)(h->wsize%dsize)/dsizepct);
       putbuf(p);

       bw = 0;                 /* max filled bucket */
       memset(state, 0, sizeof(state));
       for(m = 0; m < hmsize; m++) {
               p = getbuf(cw->cdev, hmaddr + m/BKPERBLK, Brd);
               if(!p || checktag(p, Tbuck, hmaddr + m/BKPERBLK)) {
                       print("cwstats: checktag c bucket\n");
                       if(p)
                               putbuf(p);
                       return;
               }
               b = (Bucket*)p->iobuf + m%BKPERBLK;
               ce = b->entry + CEPERBK;
               nw = 0;
               for(c = b->entry; c < ce; c++) {
                       s = c->state;
                       state[s]++;
                       if(s != Cnone && s != Cread)
                               nw++;
               }
               putbuf(p);
               if(nw > bw)
                       bw = nw;
       }
       for(s = Cnone; s < Cerror; s++)
               print("\t\t%6lld %s\n", (Wideoff)state[s], cwnames[s]);
       print("\t\tcache %2lld%% full\n", ((Wideoff)bw*100)/CEPERBK);
}

int
dumpblock(Device *dev)
{
       Iobuf *p, *cb, *p1, *p2;
       Cache *h;
       Centry *c, *ce, *bc;
       Bucket *b;
       Off m, a, bn, msize, maddr, wmax, caddr;
       int s1, s2, count;
       Cw *cw;

       cw = dev->private;
       if(cw == 0 || cw->nodump)
               return 0;

       cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
       h = (Cache*)cb->iobuf;
       msize = h->msize;
       maddr = h->maddr;
       wmax = h->wmax;
       caddr = h->caddr;
       putbuf(cb);

       for(m=msize; m>=0; m--) {
               bn = cw->dbucket + 1;
               if(bn < 0 || bn >= msize)
                       bn = 0;
               cw->dbucket = bn;
               a = maddr + bn/BKPERBLK;
               p = getbuf(cw->cdev, a, Brd);
               if(!p || checktag(p, Tbuck, a)) {
                       fprint(2, "dump: checktag c bucket\n");
                       if(p)
                               putbuf(p);
                       goto stop;
               }
               b = (Bucket*)p->iobuf + bn%BKPERBLK;
               ce = b->entry + CEPERBK;
               bc = 0;
               for(c = b->entry; c < ce; c++)
                       if(c->state == Cdump) {
                               if(bc == 0) {
                                       bc = c;
                                       continue;
                               }
                               if(c->waddr < cw->daddr) {
                                       if(bc->waddr < cw->daddr &&
                                          bc->waddr > c->waddr)
                                               bc = c;
                                       continue;
                               }
                               if(bc->waddr < cw->daddr ||
                                  bc->waddr > c->waddr)
                                       bc = c;
                       }
               if(bc) {
                       c = bc;
                       goto found;
               }
               putbuf(p);
       }
       if(cw->ncopy){
               if(chatty)
                       fprint(2, "%lld blocks copied to worm\n", (Wideoff)cw->ncopy);
               cw->ncopy = 0;
       }
       cw->nodump = 1;
       return 0;

found:
       if (conf.newcache)
               a = bn + (c - b->entry)*msize + caddr;
       else
               a = bn*CEPERBK + (c - b->entry) + caddr;
       p1 = getbuf(devnone, Cwdump1, 0);
       count = 0;

retry:
       count++;
       if(count > 10 || devread(cw->cdev, a, p1->iobuf)) {
               putbuf(p1);
               putbuf(p);
               goto stop;
       }
       m = c->waddr;
       cw->daddr = m;
       s1 = devwrite(cw->wdev, m, p1->iobuf);
       if(s1) {
               p2 = getbuf(devnone, Cwdump2, 0);
               s2 = devread(cw->wdev, m, p2->iobuf);
               if(s2) {
                       if(s1 == 0x61 && s2 == 0x60) {
                               putbuf(p2);
                               goto retry;
                       }
                       goto stop1;
               }
               if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE))
                       goto stop1;
               putbuf(p2);
       }
       /*
        * reread and compare
        */
       if(conf.dumpreread) {
               p2 = getbuf(devnone, Cwdump2, 0);
               s1 = devread(cw->wdev, m, p2->iobuf);
               if(s1)
                       goto stop1;
               if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE)) {
                       fprint(2, "reread C%lld W%lld didnt compare\n",
                               (Wideoff)a, (Wideoff)m);
                       goto stop1;
               }
               putbuf(p2);
       }

       putbuf(p1);
       c->state = Cread;
       p->flags |= Bmod;
       putbuf(p);

       if(m > wmax) {
               cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bres);
               h = (Cache*)cb->iobuf;
               if(m > h->wmax)
                       h->wmax = m;
               putbuf(cb);
       }
       cw->ncopy++;
       return 1;

stop1:
       putbuf(p2);
       putbuf(p1);
       c->state = Cdump1;
       p->flags |= Bmod;
       putbuf(p);
       return 1;

stop:
       fprint(2, "stopping dump!!\n");
       cw->nodump = 1;
       return 0;
}

void
cwinit1(Device *dev)
{
       Cw *cw;
       static int first;

       cw = dev->private;
       if(cw)
               return;

       if(first == 0) {
               cmd_install("dump", "-- make dump backup to worm", cmd_dump);
               cmd_install("statw", "-- cache/worm stats", cmd_statw);
               cmd_install("cwcmd", "subcommand -- cache/worm errata", cmd_cwcmd);
               roflag = flag_install("ro", "-- ro reads and writes");
               first = 1;
       }
       cw = ialloc(sizeof(Cw), 0);
       dev->private = cw;

       cw->allflag = 0;

       cw->dev = dev;
       cw->cdev = CDEV(dev);
       cw->wdev = WDEV(dev);
       cw->rodev = RDEV(dev);

       devinit(cw->cdev);
       devinit(cw->wdev);
}

void
cwinit(Device *dev)
{
       Cw *cw;
       Cache *h;
       Iobuf *cb, *p;
       Off l, m;

       cwinit1(dev);

       cw = dev->private;
       l = devsize(cw->wdev);
       cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bres);
       if(!cb || checktag(cb, Tcache, QPSUPER))
               panic("cwinit: checktag super");
       h = (Cache*)cb->iobuf;
       h->toytime = toytime() + SECOND(30);
       h->time = time(nil);
       m = h->wsize;
       if(l != m) {
               if(chatty)
                       fprint(2, "wdev changed size %lld to %lld\n",
                               (Wideoff)m, (Wideoff)l);
               h->wsize = l;
               cb->flags |= Bmod;
       }
       cw->wsize = l;

       for(m=0; m<h->msize; m++) {
               p = getbuf(cw->cdev, h->maddr + m/BKPERBLK, Brd);
               if(!p || checktag(p, Tbuck, h->maddr + m/BKPERBLK))
                       panic("cwinit: checktag c bucket");
               putbuf(p);
       }
       putbuf(cb);
}

Off
cwsaddr(Device *dev)
{
       Iobuf *cb;
       Off sa;

       cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
       sa = ((Cache*)cb->iobuf)->sbaddr;
       putbuf(cb);
       return sa;
}

Off
cwraddr(Device *dev)
{
       Iobuf *cb;
       Off ra;

       switch(dev->type) {
       default:
               fprint(2, "unknown dev in cwraddr %Z\n", dev);
               return 1;

       case Devcw:
               cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
               ra = ((Cache*)cb->iobuf)->cwraddr;
               break;

       case Devro:
               cb = getbuf(CDEV(dev->ro.parent), CACHE_ADDR, Brd|Bres);
               ra = ((Cache*)cb->iobuf)->roraddr;
               break;
       }
       putbuf(cb);
       return ra;
}

Devsize
cwsize(Device *dev)
{
       Iobuf *cb;
       Devsize fs;

       cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
       fs = ((Cache*)cb->iobuf)->fsize;
       putbuf(cb);
       return fs;
}

int
cwread(Device *dev, Off b, void *c)
{
       return cwio(dev, b, c, Oread) == Cerror;
}

int
cwwrite(Device *dev, Off b, void *c)
{
       return cwio(dev, b, c, Owrite) == Cerror;
}

int
roread(Device *dev, Off b, void *c)
{
       Device *d;
       int s;

       /*
        * maybe better is to try buffer pool first
        */
       d = dev->ro.parent;
       if(d == 0 || d->type != Devcw ||
          d->private == 0 || RDEV(d) != dev) {
               fprint(2, "bad rodev %Z\n", dev);
               return 1;
       }
       s = cwio(d, b, 0, Onone);
       if(s == Cdump || s == Cdump1 || s == Cread) {
               s = cwio(d, b, c, Oread);
               if(s == Cdump || s == Cdump1 || s == Cread) {
                       if(cons.flags & roflag)
                               fprint(2, "roread: %Z %lld -> %Z(hit)\n",
                                       dev, (Wideoff)b, d);
                       return 0;
               }
       }
       if(cons.flags & roflag)
               fprint(2, "roread: %Z %lld -> %Z(miss)\n",
                       dev, (Wideoff)b, WDEV(d));
       return devread(WDEV(d), b, c);
}

int
cwio(Device *dev, Off addr, void *buf, int opcode)
{
       Iobuf *p, *p1, *p2, *cb;
       Cache *h;
       Bucket *b;
       Centry *c;
       Off bn, a1, a2, max, newmax;
       int state;
       Cw *cw;

       cw = dev->private;

       cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
       h = (Cache*)cb->iobuf;
       if(toytime() >= h->toytime) {
               cb->flags |= Bmod;
               h->toytime = toytime() + SECOND(30);
               h->time = time(nil);
       }

       if(addr < 0) {
               putbuf(cb);
               return Cerror;
       }

       bn = addr % h->msize;
       a1 = h->maddr + bn/BKPERBLK;
       if (conf.newcache)
               a2 = bn + h->caddr;
       else
               a2 = bn*CEPERBK + h->caddr;
       max = h->wmax;

       putbuf(cb);
       newmax = 0;

       p = getbuf(cw->cdev, a1, Brd|Bmod);
       if(!p || checktag(p, Tbuck, a1))
               panic("cwio: checktag c bucket");
       b = (Bucket*)p->iobuf + bn%BKPERBLK;

       c = getcentry(b, addr);
       if(c == 0) {
               putbuf(p);
               fprint(2, "%Z disk cache bucket %lld is full\n",
                       cw->cdev, (Wideoff)a1);
               return Cerror;
       }
       if (conf.newcache)
               a2 += (c - b->entry) * h->msize;
       else
               a2 += c - b->entry;

       state = c->state;
       switch(opcode) {
       default:
               goto bad;

       case Onone:
               break;

       case Oread:
               switch(state) {
               default:
                       goto bad;

               case Cread:
                       if(!devread(cw->cdev, a2, buf))
                               break;
                       c->state = Cnone;

               case Cnone:
                       if(devread(cw->wdev, addr, buf)) {
                               state = Cerror;
                               break;
                       }
                       if(addr > max)
                               newmax = addr;
                       if(!devwrite(cw->cdev, a2, buf))
                               c->state = Cread;
                       break;

               case Cdirty:
               case Cdump:
               case Cdump1:
               case Cwrite:
                       if(devread(cw->cdev, a2, buf))
                               state = Cerror;
                       break;
               }
               break;

       case Owrite:
               switch(state) {
               default:
                       goto bad;

               case Cdump:
               case Cdump1:
                       /*
                        * this is hard part -- a dump block must be
                        * sent to the worm if it is rewritten.
                        * if this causes an error, there is no
                        * place to save the dump1 data. the block
                        * is just reclassified as 'dump1' (botch)
                        */
                       p1 = getbuf(devnone, Cwio1, 0);
                       if(devread(cw->cdev, a2, p1->iobuf)) {
                               putbuf(p1);
                               fprint(2, "cwio: write induced dump error - r cache\n");

                       casenone:
                               if(devwrite(cw->cdev, a2, buf)) {
                                       state = Cerror;
                                       break;
                               }
                               c->state = Cdump1;
                               break;
                       }
                       if(devwrite(cw->wdev, addr, p1->iobuf)) {
                               p2 = getbuf(devnone, Cwio2, 0);
                               if(devread(cw->wdev, addr, p2->iobuf)) {
                                       putbuf(p1);
                                       putbuf(p2);
                                       fprint(2, "cwio: write induced dump error - r+w worm\n");
                                       goto casenone;
                               }
                               if(memcmp(p1->iobuf, p2->iobuf, RBUFSIZE)) {
                                       putbuf(p1);
                                       putbuf(p2);
                                       fprint(2, "cwio: write induced dump error - w worm\n");
                                       goto casenone;
                               }
                               putbuf(p2);
                       }
                       putbuf(p1);
                       c->state = Cread;
                       if(addr > max)
                               newmax = addr;
                       cw->ncopy++;

               case Cnone:
               case Cread:
                       if(devwrite(cw->cdev, a2, buf)) {
                               state = Cerror;
                               break;
                       }
                       c->state = Cwrite;
                       break;

               case Cdirty:
               case Cwrite:
                       if(devwrite(cw->cdev, a2, buf))
                               state = Cerror;
                       break;
               }
               break;

       case Ogrow:
               if(state != Cnone) {
                       fprint(2, "%Z for block %lld cwgrow with state = %s\n",
                               cw->cdev, (Wideoff)addr, cwnames[state]);
                       break;
               }
               c->state = Cdirty;
               break;

       case Odump:
               if(state != Cdirty) {   /* BOTCH */
                       fprint(2, "%Z for block %lld cwdump with state = %s\n",
                               cw->cdev, (Wideoff)addr, cwnames[state]);
                       break;
               }
               c->state = Cdump;
               cw->ndump++;    /* only called from dump command */
               break;

       case Orele:
               if(state != Cwrite) {
                       if(state != Cdump1)
                               fprint(2, "%Z for block %lld cwrele with state = %s\n",
                                       cw->cdev, (Wideoff)addr, cwnames[state]);
                       break;
               }
               c->state = Cnone;
               break;

       case Ofree:
               if(state == Cwrite || state == Cread)
                       c->state = Cnone;
               break;
       }
       if(chatty > 1)
               fprint(2, "cwio: %Z %lld s=%s o=%s ns=%s\n",
                       dev, (Wideoff)addr, cwnames[state],
                       cwnames[opcode],
                       cwnames[c->state]);
       putbuf(p);
       if(newmax) {
               cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bres);
               h = (Cache*)cb->iobuf;
               if(newmax > h->wmax)
                       h->wmax = newmax;
               putbuf(cb);
       }
       return state;

bad:
       fprint(2, "%Z block %lld cw state = %s; cw opcode = %s",
               dev, (Wideoff)addr, cwnames[state], cwnames[opcode]);
       return Cerror;
}


int
cwgrow(Device *dev, Superb *sb, int)
{
       Iobuf *cb;
       Cache *h;
       Off fs, nfs, ws;

       cb = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bmod|Bres);
       h = (Cache*)cb->iobuf;
       ws = h->wsize;
       fs = h->fsize;
       if(fs >= ws){
               putbuf(cb);
               return 0;
       }
       nfs = fs + ADDFREE;
       if(nfs >= ws)
               nfs = ws;
       h->fsize = nfs;
       putbuf(cb);

       sb->fsize = nfs;
       for(nfs--; nfs>=fs; nfs--)
               switch(cwio(dev, nfs, 0, Ogrow)) {
               case Cerror:
                       return 0;
               case Cnone:
                       addfree(dev, nfs, sb);
               }
       return 1;
}

int
cwfree(Device *dev, Off addr)
{
       int state;

       if(dev->type == Devcw) {
               state = cwio(dev, addr, 0, Ofree);
               if(state != Cdirty)
                       return 1;       /* do not put in freelist */
       }
       return 0;                       /* put in freelist */
}

#ifdef unused
int
bktcheck(Bucket *b)
{
       Centry *c, *c1, *c2, *ce;
       int err;

       err = 0;
       if(b->agegen < CEPERBK || b->agegen > MAXAGE) {
               print("agegen %ld\n", b->agegen);
               err = 1;
       }

       ce = b->entry + CEPERBK;
       c1 = 0;         /* lowest age last pass */
       for(;;) {
               c2 = 0;         /* lowest age this pass */
               for(c = b->entry; c < ce; c++) {
                       if(c1 != 0 && c != c1) {
                               if(c->age == c1->age) {
                                       print("same age %d\n", c->age);
                                       err = 1;
                               }
                               if(c1->waddr == c->waddr)
                               if(c1->state != Cnone)
                               if(c->state != Cnone) {
                                       print("same waddr %lld\n",
                                               (Wideoff)c->waddr);
                                       err = 1;
                               }
                       }
                       if(c1 != 0 && c->age <= c1->age)
                               continue;
                       if(c2 == 0 || c->age < c2->age)
                               c2 = c;
               }
               if(c2 == 0)
                       break;
               c1 = c2;
               if(c1->age >= b->agegen) {
                       print("age >= generator %d %ld\n", c1->age, b->agegen);
                       err = 1;
               }
       }
       return err;
}
#endif

void
resequence(Bucket *b)
{
       Centry *c, *ce, *cr;
       int age, i;

       ce = b->entry + CEPERBK;
       for(c = b->entry; c < ce; c++) {
               c->age += CEPERBK;
               if(c->age < CEPERBK)
                       c->age = MAXAGE;
       }
       b->agegen += CEPERBK;

       age = 0;
       for(i=0;; i++) {
               cr = 0;
               for(c = b->entry; c < ce; c++) {
                       if(c->age < i)
                               continue;
                       if(cr == 0 || c->age < age) {
                               cr = c;
                               age = c->age;
                       }
               }
               if(cr == 0)
                       break;
               cr->age = i;
       }
       b->agegen = i;
       cons.nreseq++;
}

Centry*
getcentry(Bucket *b, Off addr)
{
       Centry *c, *ce, *cr;
       int s, age;

       /*
        * search for cache hit
        * find oldest block as byproduct
        */
       ce = b->entry + CEPERBK;
       age = 0;
       cr = 0;
       for(c = b->entry; c < ce; c++) {
               s = c->state;
               if(s == Cnone) {
                       cr = c;
                       age = 0;
                       continue;
               }
               if(c->waddr == addr)
                       goto found;
               if(s == Cread)
                       if(cr == 0 || c->age < age) {
                               cr = c;
                               age = c->age;
                       }
       }

       /*
        * remap entry
        */
       c = cr;
       if(c == 0)
               return 0;       /* bucket is full */

       c->state = Cnone;
       c->waddr = addr;

found:
       /*
        * update the age to get filo cache.
        * small number in age means old
        */
       if(!cons.noage || c->state == Cnone) {
               age = b->agegen;
               c->age = age;
               age++;
               b->agegen = age;
               if(age < 0 || age >= MAXAGE)
                       resequence(b);
       }
       return c;
}

/*
* ream the cache
* calculate new buckets
*/
Iobuf*
cacheinit(Device *dev)
{
       Iobuf *cb, *p;
       Cache *h;
       Device *cdev;
       Off m;

       if(chatty)
               print("cache init %Z\n", dev);
       cdev = CDEV(dev);
       devinit(cdev);

       cb = getbuf(cdev, CACHE_ADDR, Bmod|Bres);
       memset(cb->iobuf, 0, RBUFSIZE);
       settag(cb, Tcache, QPSUPER);
       h = (Cache*)cb->iobuf;

       /*
        * calculate csize such that
        * tsize = msize/BKPERBLK + csize and
        * msize = csize/CEPERBK
        */
       h->maddr = CACHE_ADDR + 1;
       m = devsize(cdev) - h->maddr;
       h->csize = ((Devsize)(m-1) * CEPERBK*BKPERBLK) / (CEPERBK*BKPERBLK+1);
       h->msize = h->csize/CEPERBK - 5;
       while(!prime(h->msize))
               h->msize--;
       h->csize = h->msize*CEPERBK;
       h->caddr = h->maddr + (h->msize+BKPERBLK-1)/BKPERBLK;
       h->wsize = devsize(WDEV(dev));

       if(h->msize <= 0)
               panic("cache too small");
       if(h->caddr + h->csize > m)
               panic("cache size error");

       /*
        * setup cache map
        */
       for(m=h->maddr; m<h->caddr; m++) {
               p = getbuf(cdev, m, Bmod);
               memset(p->iobuf, 0, RBUFSIZE);
               settag(p, Tbuck, m);
               putbuf(p);
       }
       return cb;
}

Off
getstartsb(Device *dev)
{
       Filsys *f;
       Startsb *s;

       for(f=filsys; f->name; f++){
               if(devcmpr(f->dev, dev) == 0) {
                       for(s=startsb; s->name; s++)
                               if(strcmp(f->name, s->name) == 0)
                                       return s->startsb;
                       fprint(2, "getstartsb: no special starting superblock for %Z %s\n",
                               dev, f->name);
                       return FIRST;
               }
       }
       fprint(2, "getstartsb: no filsys for device %Z\n", dev);
       return FIRST;
}

/*
* ream the cache
* calculate new buckets
* get superblock from
* last worm dump block.
*/
void
cwrecover(Device *dev)
{
       Iobuf *p, *cb;
       Cache *h;
       Superb *s;
       Off m, baddr;
       Device *wdev;

       if(chatty)
               print("cwrecover %Z\n", dev);
       cwinit1(dev);
       wdev = WDEV(dev);

       p = getbuf(devnone, Cwxx1, 0);
       s = (Superb*)p->iobuf;
       baddr = 0;
       m = getstartsb(dev);
       localconfinit();
       if(conf.firstsb)
               m = conf.firstsb;
       for(;;) {
               memset(p->iobuf, 0, RBUFSIZE);
               if(devread(wdev, m, p->iobuf) ||
                  checktag(p, Tsuper, QPSUPER))
                       break;
               baddr = m;
               m = s->next;
               if(chatty)
                       print("dump %lld is good; %lld next\n", (Wideoff)baddr, (Wideoff)m);
               if(baddr == conf.recovsb)
                       break;
       }
       putbuf(p);
       if(!baddr)
               panic("recover: no superblock");

       p = getbuf(wdev, baddr, Brd);
       s = (Superb*)p->iobuf;

       cb = cacheinit(dev);
       h = (Cache*)cb->iobuf;
       h->sbaddr = baddr;
       h->cwraddr = s->cwraddr;
       h->roraddr = s->roraddr;
       h->fsize = s->fsize + 100;              /* this must be conservative */
       if(conf.recovcw)
               h->cwraddr = conf.recovcw;
       if(conf.recovro)
               h->roraddr = conf.recovro;

       putbuf(cb);
       putbuf(p);

       p = getbuf(dev, baddr, Brd|Bmod);
       s = (Superb*)p->iobuf;

       memset(&s->fbuf, 0, sizeof(s->fbuf));
       s->fbuf.free[0] = 0;
       s->fbuf.nfree = 1;
       s->tfree = 0;
       if(conf.recovcw)
               s->cwraddr = conf.recovcw;
       if(conf.recovro)
               s->roraddr = conf.recovro;

       putbuf(p);
}

/*
* ream the cache
* calculate new buckets
* initialize superblock.
*/
void
cwream(Device *dev)
{
       Iobuf *p, *cb;
       Cache *h;
       Superb *s;
       Off m, baddr;
       Device *cdev;

       if(chatty)
               print("cwream %Z\n", dev);
       cwinit1(dev);
       cdev = CDEV(dev);
       devinit(cdev);

       baddr = FIRST;  /*      baddr   = super addr
                               baddr+1 = cw root
                               baddr+2 = ro root
                               baddr+3 = reserved next superblock */

       cb = cacheinit(dev);
       h = (Cache*)cb->iobuf;

       h->sbaddr = baddr;
       h->cwraddr = baddr+1;
       h->roraddr = baddr+2;
       h->fsize = 0;   /* prevents superream from freeing */

       putbuf(cb);

       for(m=0; m<3; m++)
               cwio(dev, baddr+m, 0, Ogrow);
       superream(dev, baddr);
       rootream(dev, baddr+1);                 /* cw root */
       rootream(dev, baddr+2);                 /* ro root */

       cb = getbuf(cdev, CACHE_ADDR, Brd|Bmod|Bres);
       h = (Cache*)cb->iobuf;
       h->fsize = baddr+4;
       putbuf(cb);

       p = getbuf(dev, baddr, Brd|Bmod|Bimm);
       s = (Superb*)p->iobuf;
       s->last = baddr;
       s->cwraddr = baddr+1;
       s->roraddr = baddr+2;
       s->next = baddr+3;
       s->fsize = baddr+4;
       putbuf(p);

       for(m=0; m<3; m++)
               cwio(dev, baddr+m, 0, Odump);

       /* write superblock to worm */
       while(dumpblock(dev))
               ;
}

Off
rewalk1(Cw *cw, Off addr, int slot, Wpath *up)
{
       Iobuf *p, *p1;
       Dentry *d;

       if(up == 0)
               return cwraddr(cw->dev);
       up->addr = rewalk1(cw, up->addr, up->slot, up->up);
       p = getbuf(cw->dev, up->addr, Brd|Bmod);
       d = getdir(p, up->slot);
       if(!d || !(d->mode & DALLOC)) {
               fprint(2, "rewalk1 1\n");
               if(p)
                       putbuf(p);
               return addr;
       }
       p1 = dnodebuf(p, d, slot/DIRPERBUF, 0, 0);
       if(!p1) {
               fprint(2, "rewalk1 2\n");
               if(p)
                       putbuf(p);
               return addr;
       }
       if(chatty > 1)
               fprint(2, "rewalk1 %lld to %lld \"%s\"\n",
                       (Wideoff)addr, (Wideoff)p1->addr, d->name);
       addr = p1->addr;
       p1->flags |= Bmod;
       putbuf(p1);
       putbuf(p);
       return addr;
}

Off
rewalk2(Cw *cw, Off addr, int slot, Wpath *up)
{
       Iobuf *p, *p1;
       Dentry *d;

       if(up == 0)
               return cwraddr(cw->rodev);
       up->addr = rewalk2(cw, up->addr, up->slot, up->up);
       p = getbuf(cw->rodev, up->addr, Brd);
       d = getdir(p, up->slot);
       if(!d || !(d->mode & DALLOC)) {
               fprint(2, "rewalk2 1\n");
               if(p)
                       putbuf(p);
               return addr;
       }
       p1 = dnodebuf(p, d, slot/DIRPERBUF, 0, 0);
       if(!p1) {
               fprint(2, "rewalk2 2\n");
               if(p)
                       putbuf(p);
               return addr;
       }
       if(chatty > 1)
               fprint(2, "rewalk2 %lld to %lld \"%s\"\n",
                       (Wideoff)addr, (Wideoff)p1->addr, d->name);
       addr = p1->addr;
       putbuf(p1);
       putbuf(p);
       return addr;
}

void
rewalk(Cw *cw)
{
       int h;
       File *f;

       for(h=0; h<nelem(flist); h++)
               for(f=flist[h]; f; f=f->next) {
                       if(!f->fs)
                               continue;
                       if(cw->dev == f->fs->dev)
                               f->addr = rewalk1(cw, f->addr, f->slot, f->wpath);
                       else
                       if(cw->rodev == f->fs->dev)
                               f->addr = rewalk2(cw, f->addr, f->slot, f->wpath);
               }
}

Off
split(Cw *cw, Iobuf *p, Off addr)
{
       Off na;
       int state;

       na = 0;
       if(p && (p->flags & Bmod)) {
               p->flags |= Bimm;
               putbuf(p);
               p = 0;
       }

       state = cwio(cw->dev, addr, 0, Onone);  /* read the state (twice?) */
       switch(state) {
       default:
               panic("split: unknown state %s", cwnames[state]);

       case Cerror:
       case Cnone:
       case Cdump:
       case Cread:
               break;

       case Cdump1:
       case Cwrite:
               /* worm full */
               if(cw->fsize >= cw->wsize)
                       break;

               /*
                * botch.. could be done by relabeling
                */
               if(!p){
                       p = getbuf(cw->dev, addr, Brd);
                       if(p == nil) {
                               fprint(2, "split: null getbuf\n");
                               break;
                       }
               }
               na = cw->fsize++;
               cwio(cw->dev, na, 0, Ogrow);
               cwio(cw->dev, na, p->iobuf, Owrite);
               cwio(cw->dev, na, 0, Odump);
               cwio(cw->dev, addr, 0, Orele);
               break;

       case Cdirty:
               cwio(cw->dev, addr, 0, Odump);
               break;
       }
       if(p)
               putbuf(p);
       return na;
}

int
isdirty(Cw *cw, Iobuf *p, Off addr, int tag)
{
       int s;

       if(p && (p->flags & Bmod))
               return 1;
       s = cwio(cw->dev, addr, 0, Onone);
       if(s == Cdirty || s == Cwrite)
               return 1;
       if(tag >= Tind1 && tag <= Tmaxind)
               /* botch, get these modified */
               if(s != Cnone)
                       return 1;
       return 0;
}

Off
cwrecur(Cw *cw, Off addr, int tag, int tag1, Off qp)
{
       Iobuf *p;
       Dentry *d;
       Off qp1;
       int i, j, shouldstop;
       Off na;
       char *np;

       shouldstop = 0;
       p = getbuf(cw->dev, addr, Bprobe);
       if(!isdirty(cw, p, addr, tag)) {
               if(!cw->all) {
                       if(chatty > 1)
                               fprint(2, "cwrecur: %lld t=%s not dirty %s\n",
                                       (Wideoff)addr, tagnames[tag], cw->name);
                       if(p)
                               putbuf(p);
                       return 0;
               }
               shouldstop = 1;
       }
       if(chatty > 1)
               fprint(2, "cwrecur: %lld t=%s %s\n",
                       (Wideoff)addr, tagnames[tag], cw->name);
       if(cw->depth >= 100) {
               fprint(2, "dump depth too great %s\n", cw->name);
               if(p)
                       putbuf(p);
               return 0;
       }
       cw->depth++;

       switch(tag) {
       default:
               fprint(2, "cwrecur: unknown tag %d %s\n", tag, cw->name);
               break;

       case Tfile:
               if(p && checktag(p, tag, qp))
                       fprint(2, "cwrecur: Tfile %s\n", cw->name);
               break;

       case Tdir:
               if(cw->depth > 1) {
                       cw->namepad[0] = 0;     /* force room */
                       np = strchr(cw->name, 0);
                       *np++ = '/';
               } else {
                       np = 0; /* set */
                       cw->name[0] = 0;
               }
               if(!p)
                       p = getbuf(cw->dev, addr, Brd);
               if(!p || checktag(p, tag, qp)) {
                       fprint(2, "cwrecur: Tdir %s\n", cw->name);
                       break;
               }
               for(i=0; i<DIRPERBUF; i++) {
                       d = getdir(p, i);
                       if((d->mode & (DALLOC|DTMP)) != DALLOC)
                               continue;
                       if(np)
                               strncpy(np, d->name, NAMELEN);
                       else if(i > 0)
                               fprint(2, "cwrecur: root with >1 directory\n");
                       qp1 = d->qid.path;
                       tag1 = Tfile;
                       if(d->mode & DDIR){
                               qp1 ^= QPDIR;
                               tag1 = Tdir;
                       }
                       for(j=0; j<NDBLOCK; j++) {
                               na = d->dblock[j];
                               if(na) {
                                       na = cwrecur(cw, na, tag1, 0, qp1);
                                       if(na) {
                                               d->dblock[j] = na;
                                               p->flags |= Bmod;
                                       }
                               }
                       }
                       for (j = 0; j < NIBLOCK; j++) {
                               na = d->iblocks[j];
                               if(na) {
                                       na = cwrecur(cw, na, Tind1+j, tag1, qp1);
                                       if(na) {
                                               d->iblocks[j] = na;
                                               p->flags |= Bmod;
                                       }
                               }
                       }
               }
               break;

       case Tind1:
               j = tag1;
               tag1 = 0;
               goto tind;

       case Tind2:
#ifndef COMPAT32
       case Tind3:
       case Tind4:
       /* add more Tind tags here ... */
#endif
               j = tag-1;
       tind:
               if(!p)
                       p = getbuf(cw->dev, addr, Brd);
               if(!p || checktag(p, tag, qp)) {
                       fprint(2, "cwrecur: Tind %s\n", cw->name);
                       break;
               }
               for(i=0; i<INDPERBUF; i++) {
                       na = ((Off *)p->iobuf)[i];
                       if(na) {
                               na = cwrecur(cw, na, j, tag1, qp);
                               if(na) {
                                       ((Off *)p->iobuf)[i] = na;
                                       p->flags |= Bmod;
                               }
                       }
               }
               break;
       }

       na = split(cw, p, addr);

       cw->depth--;

       if(na){
               if(shouldstop){
                       if(cw->allflag && cw->falsehits < 10)
                               fprint(2, "shouldstop %lld %lld t=%s %s\n",
                                       (Wideoff)addr, (Wideoff)na,
                                       tagnames[tag], cw->name);
                       cw->falsehits++;
               }
       }

       return na;
}

Timet   nextdump(Timet t);

void
cfsdump(Filsys *fs)
{
       long m, n, i;
       Off orba, rba, oroa, roa, sba, a;
       Timet tim;
       char tstr[20];
       Iobuf *pr, *p1, *p;
       Dentry *dr, *d1, *d;
       Cache *h;
       Superb *s;
       Cw *cw;

       if(fs->dev->type != Devcw) {
               fprint(2, "cant dump; not cw device: %Z\n", fs->dev);
               return;
       }
       cw = fs->dev->private;
       if(cw == 0) {
               fprint(2, "cant dump: has not been inited: %Z\n", fs->dev);
               return;
       }

       tim = toytime();
       wlock(&mainlock);               /* dump */

       /*
        * set up static structure
        * with frequent variables
        */
       cw->ndump = 0;
       cw->name[0] = 0;
       cw->depth = 0;

       /*
        * cw root
        */
       sync("before dump");
       cw->fsize = cwsize(cw->dev);
       orba = cwraddr(cw->dev);
       if(chatty)
               fprint(2, "cwroot %lld", (Wideoff)orba);
       cons.noage = 1;
       cw->all = cw->allflag | noatime | noatimeset;
       noatimeset = 0;
       rba = cwrecur(cw, orba, Tdir, 0, QPROOT);
       if(rba == 0)
               rba = orba;
       if(chatty)
               fprint(2, "->%lld\n", (Wideoff)rba);
       sync("after cw");

       /*
        * partial super block
        */
       p = getbuf(cw->dev, cwsaddr(cw->dev), Brd|Bmod|Bimm);
       if(!p || checktag(p, Tsuper, QPSUPER))
               goto bad;
       s = (Superb*)p->iobuf;
       s->fsize = cw->fsize;
       s->cwraddr = rba;
       putbuf(p);

       /*
        * partial cache block
        */
       p = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bimm|Bres);
       if(!p || checktag(p, Tcache, QPSUPER))
               goto bad;
       h = (Cache*)p->iobuf;
       h->fsize = cw->fsize;
       h->cwraddr = rba;
       putbuf(p);

       if(cw->fsize+50 > cw->wsize){
               fprint(2, "dump: worm full after dump\n");
               goto done;
       }

       /*
        * ro root
        */
       oroa = cwraddr(cw->rodev);
       pr = getbuf(cw->dev, oroa, Brd|Bmod);
       if(!pr || checktag(pr, Tdir, QPROOT))
               goto bad;
       dr = getdir(pr, 0);

       datestr(tstr, time(nil));       /* tstr = "yyyymmdd" */
       n = 0;
       for(a=0;; a++) {
               p1 = dnodebuf(pr, dr, a, Tdir, 0);
               if(!p1 || checktag(p1, Tdir, QPNONE))
                       goto bad;
               n++;
               for(i=0; i<DIRPERBUF; i++) {
                       d1 = getdir(p1, i);
                       if(!d1)
                               goto bad;
                       if(!(d1->mode & DALLOC))
                               goto found1;
                       if(!memcmp(d1->name, tstr, 4))
                               goto found2;    /* found entry */
               }
               putbuf(p1);
       }

       /*
        * no year directory, create one
        */
found1:
       p = getbuf(cw->dev, rba, Brd);
       if(!p || checktag(p, Tdir, QPROOT))
               goto bad;
       d = getdir(p, 0);
       d1->qid = d->qid;
       d1->qid.version += n;
       memmove(d1->name, tstr, 4);
       d1->mode = d->mode;
       d1->uid = d->uid;
       d1->gid = d->gid;
       putbuf(p);
       accessdir(pr, dr, FWRITE, 0);

       /*
        * put mmdd[count] in year directory
        */
found2:
       accessdir(pr, dr, FREAD, 0);
       putbuf(pr);
       pr = p1;
       dr = d1;

       n = 0;
       m = 0;
       for(a=0;; a++) {
               p1 = dnodebuf(pr, dr, a, Tdir, 0);
               if(!p1 || checktag(p1, Tdir, QPNONE))
                       goto bad;
               n++;
               for(i=0; i<DIRPERBUF; i++) {
                       d1 = getdir(p1, i);
                       if(!d1)
                               goto bad;
                       if(!(d1->mode & DALLOC))
                               goto found;
                       if(!memcmp(d1->name, tstr+4, 4))
                               m++;
               }
               putbuf(p1);
       }

       /*
        * empty slot put in root
        */
found:
       if(m)   /* how many dumps this date */
               sprint(tstr+8, "%ld", m);

       p = getbuf(cw->dev, rba, Brd);
       if(!p || checktag(p, Tdir, QPROOT))
               goto bad;
       d = getdir(p, 0);
       *d1 = *d;                               /* qid is QPROOT */
       putbuf(p);
       strcpy(d1->name, tstr+4);
       d1->qid.version += n;
       accessdir(p1, d1, FWRITE, 0);
       putbuf(p1);
       accessdir(pr, dr, FWRITE, 0);
       putbuf(pr);

       cw->fsize = cwsize(cw->dev);
       oroa = cwraddr(cw->rodev);              /* probably redundant */
       if(chatty)
               fprint(2, "roroot %lld", (Wideoff)oroa);

       cons.noage = 0;
       cw->all = 0;
       roa = cwrecur(cw, oroa, Tdir, 0, QPROOT);
       if(roa == 0)
               roa = oroa;
       if(chatty)
               fprint(2, "->%lld /%.4s/%s\n", (Wideoff)roa, tstr, tstr+4);
       sync("after ro");

       /*
        * final super block
        */
       a = cwsaddr(cw->dev);
       if(chatty)
               fprint(2, "sblock %lld", (Wideoff)a);
       p = getbuf(cw->dev, a, Brd|Bmod|Bimm);
       if(!p || checktag(p, Tsuper, QPSUPER))
               goto bad;
       s = (Superb*)p->iobuf;
       s->last = a;
       sba = s->next;
       s->next = cw->fsize++;
       s->fsize = cw->fsize;
       s->roraddr = roa;

       cwio(cw->dev, sba, 0, Ogrow);
       cwio(cw->dev, sba, p->iobuf, Owrite);
       cwio(cw->dev, sba, 0, Odump);
       if(chatty)
               fprint(2, "->%lld (->%lld)\n", (Wideoff)sba, (Wideoff)s->next);

       putbuf(p);

       /*
        * final cache block
        */
       p = getbuf(cw->cdev, CACHE_ADDR, Brd|Bmod|Bimm|Bres);
       if(!p || checktag(p, Tcache, QPSUPER))
               goto bad;
       h = (Cache*)p->iobuf;
       h->fsize = cw->fsize;
       h->roraddr = roa;
       h->sbaddr = sba;
       putbuf(p);

done:
       rewalk(cw);
       sync("all done");

       if(chatty){
               fprint(2, "%lld blocks queued for worm\n", (Wideoff)cw->ndump);
               fprint(2, "%lld falsehits\n", (Wideoff)cw->falsehits);
       }
       cw->nodump = 0;

       /*
        * extend all of the locks
        */
       tim = toytime() - tim;
       for(i=0; i<NTLOCK; i++)
               if(tlocks[i].time > 0)
                       tlocks[i].time += tim;

       wunlock(&mainlock);
       nextdump(time(nil));
       return;

bad:
       panic("dump: bad");
}

void
mvstates(Device *dev, int s1, int s2, int side)
{
       Iobuf *p, *cb;
       Cache *h;
       Bucket *b;
       Centry *c, *ce;
       Off m, lo, hi, msize, maddr;
       Cw *cw;

       cw = dev->private;
       lo = 0;
       hi = lo + devsize(dev->cw.w);   /* size of all sides totalled */
       if(side >= 0) {
               /* operate on only a single disc side */
               Sidestarts ss;

               wormsidestarts(dev, side, &ss);
               lo = ss.sstart;
               hi = ss.s1start;
       }
       cb = getbuf(cw->cdev, CACHE_ADDR, Brd|Bres);
       if(!cb || checktag(cb, Tcache, QPSUPER))
               panic("mvstates: checktag super");
       h = (Cache*)cb->iobuf;
       msize = h->msize;
       maddr = h->maddr;
       putbuf(cb);

       for(m=0; m<msize; m++) {
               p = getbuf(cw->cdev, maddr + m/BKPERBLK, Brd|Bmod);
               if(!p || checktag(p, Tbuck, maddr + m/BKPERBLK))
                       panic("mvstates: checktag c bucket");
               b = (Bucket*)p->iobuf + m%BKPERBLK;
               ce = b->entry + CEPERBK;
               for(c=b->entry; c<ce; c++)
                       if(c->state == s1 && c->waddr >= lo && c->waddr < hi)
                               c->state = s2;
               putbuf(p);
       }
}

void
prchain(Device *dev, Off m, int flg)
{
       Iobuf *p;
       Superb *s;

       if(m == 0) {
               if(flg)
                       m = cwsaddr(dev);
               else
                       m = getstartsb(dev);
       }
       p = getbuf(devnone, Cwxx2, 0);
       s = (Superb*)p->iobuf;
       for(;;) {
               memset(p->iobuf, 0, RBUFSIZE);
               if(devread(WDEV(dev), m, p->iobuf) ||
                  checktag(p, Tsuper, QPSUPER))
                       break;
               if(flg) {
                       print("dump %lld is good; %lld prev\n", (Wideoff)m,
                               (Wideoff)s->last);
                       print("\t%lld cwroot; %lld roroot\n",
                               (Wideoff)s->cwraddr, (Wideoff)s->roraddr);
                       if(m <= s->last)
                               break;
                       m = s->last;
               } else {
                       print("dump %lld is good; %lld next\n", (Wideoff)m,
                               (Wideoff)s->next);
                       print("\t%lld cwroot; %lld roroot\n",
                               (Wideoff)s->cwraddr, (Wideoff)s->roraddr);
                       if(m >= s->next)
                               break;
                       m = s->next;
               }
       }
       putbuf(p);
}

void
touchsb(Device *dev)
{
       Iobuf *p;
       Off m;

       m = cwsaddr(dev);
       p = getbuf(devnone, Cwxx2, 0);

       memset(p->iobuf, 0, RBUFSIZE);
       if(devread(WDEV(dev), m, p->iobuf) ||
          checktag(p, Tsuper, QPSUPER))
               fprint(2, "%Z block %lld WORM SUPER BLOCK READ FAILED\n",
                       WDEV(dev), (Wideoff)m);
       else if(chatty)
               print("%Z touch superblock %lld\n", WDEV(dev), (Wideoff)m);
       putbuf(p);
}

void
storesb(Device *dev, Off last, int doit)
{
       Iobuf *ph, *ps;
       Cache *h;
       Superb *s;
       Off sbaddr, qidgen;

       sbaddr = cwsaddr(dev);

       ps = getbuf(devnone, Cwxx2, 0);
       if(!ps) {
               fprint(2, "storesb: getbuf\n");
               return;
       }

       /*
        * try to read last sb
        */
       memset(ps->iobuf, 0, RBUFSIZE);
       if(devread(WDEV(dev), last, ps->iobuf) ||
          checktag(ps, Tsuper, QPSUPER))
               print("read last failed\n");
       else
               print("read last succeeded\n");

       s = (Superb*)ps->iobuf;
       qidgen = s->qidgen;
       if(qidgen == 0)
               qidgen = 0x31415;
       qidgen += 1000;
       if(s->next != sbaddr)
               print("next(last) is not sbaddr %lld %lld\n",
                       (Wideoff)s->next, (Wideoff)sbaddr);
       else
               print("next(last) is sbaddr\n");

       /*
        * read cached superblock
        */
       ph = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
       if(!ph || checktag(ph, Tcache, QPSUPER)) {
               print("storesb: checktag super\n");
               if(ph)
                       putbuf(ph);
               putbuf(ps);
               return;
       } else
               print("read cached sb succeeded\n");

       h = (Cache*)ph->iobuf;

       memset(ps->iobuf, 0, RBUFSIZE);
       settag(ps, Tsuper, QPSUPER);
       ps->flags = 0;
       s = (Superb*)ps->iobuf;

       s->cwraddr = h->cwraddr;
       s->roraddr = h->roraddr;
       s->fsize = h->fsize;
       s->fstart = 2;
       s->last = last;
       s->next = h->roraddr+1;

       s->qidgen = qidgen;
       putbuf(ph);

       if(s->fsize-1 != s->next ||
          s->fsize-2 != s->roraddr ||
          s->fsize-5 != s->cwraddr) {
               print("addrs not in relationship %lld %lld %lld %lld\n",
                       (Wideoff)s->cwraddr, (Wideoff)s->roraddr,
                       (Wideoff)s->next, (Wideoff)s->fsize);
               putbuf(ps);
               return;
       } else
               print("addresses in relation\n");

       if(doit)
       if(devwrite(WDEV(dev), sbaddr, ps->iobuf))
               print("%Z block %lld WORM SUPER BLOCK WRITE FAILED\n",
                       WDEV(dev), (Wideoff)sbaddr);
       ps->flags = 0;
       putbuf(ps);
}

void
savecache(Device *dev)
{
       Iobuf *p, *cb;
       Cache *h;
       Bucket *b;
       Centry *c, *ce;
       long n, left;
       Off m, maddr, msize, *longp, nbyte;
       Device *cdev;

       if(walkto("/adm/cache") || con_open(FID2, OWRITE|OTRUNC)) {
               fprint(2, "cant open /adm/cache\n");
               return;
       }
       cdev = CDEV(dev);
       cb = getbuf(cdev, CACHE_ADDR, Brd|Bres);
       if(!cb || checktag(cb, Tcache, QPSUPER))
               panic("savecache: checktag super");
       h = (Cache*)cb->iobuf;
       msize = h->msize;
       maddr = h->maddr;
       putbuf(cb);

       n = BUFSIZE;                    /* calculate write size */
       if(n > MAXDAT)
               n = MAXDAT;

       cb = getbuf(devnone, Cwxx4, 0);
       longp = (Off *)cb->iobuf;
       left = n/sizeof(Off);
       cons.offset = 0;

       for(m=0; m<msize; m++) {
               if(left < BKPERBLK) {
                       nbyte = (n/sizeof(Off) - left) * sizeof(Off);
                       con_write(FID2, cb->iobuf, cons.offset, nbyte);
                       cons.offset += nbyte;
                       longp = (Off *)cb->iobuf;
                       left = n/sizeof(Off);
               }
               p = getbuf(cdev, maddr + m/BKPERBLK, Brd);
               if(!p || checktag(p, Tbuck, maddr + m/BKPERBLK))
                       panic("savecache: checktag c bucket");
               b = (Bucket*)p->iobuf + m%BKPERBLK;
               ce = b->entry + CEPERBK;
               for(c = b->entry; c < ce; c++)
                       if(c->state == Cread) {
                               *longp++ = c->waddr;
                               left--;
                       }
               putbuf(p);
       }
       nbyte = (n/sizeof(Off) - left) * sizeof(Off);
       con_write(FID2, cb->iobuf, cons.offset, nbyte);
       putbuf(cb);
}

void
loadcache(Device *dev, int dskno)
{
       Iobuf *p, *cb;
       Off m, nbyte, *longp, count;
       Sidestarts ss;

       if(walkto("/adm/cache") || con_open(FID2, OREAD)) {
               fprint(2, "cant open /adm/cache\n");
               return;
       }

       cb = getbuf(devnone, Cwxx4, 0);
       cons.offset = 0;
       count = 0;

       if (dskno >= 0)
               wormsidestarts(dev, dskno, &ss);
       for(;;) {
               memset(cb->iobuf, 0, BUFSIZE);
               nbyte = con_read(FID2, cb->iobuf, cons.offset, 100) / sizeof(Off);
               if(nbyte <= 0)
                       break;
               cons.offset += nbyte * sizeof(Off);
               longp = (Off *)cb->iobuf;
               while(nbyte > 0) {
                       m = *longp++;
                       nbyte--;
                       if(m == 0)
                               continue;
                       /* if given a diskno, restrict to just that disc side */
                       if(dskno < 0 || m >= ss.sstart && m < ss.s1start) {
                               p = getbuf(dev, m, Brd);
                               if(p)
                                       putbuf(p);
                               count++;
                       }
               }
       }
       putbuf(cb);
       print("%lld blocks loaded from worm %d\n", (Wideoff)count, dskno);
}

void
morecache(Device *dev, int dskno, Off size)
{
       Iobuf *p;
       Off m, ml, mh, mm, count;
       Cache *h;
       Sidestarts ss;

       p = getbuf(CDEV(dev), CACHE_ADDR, Brd|Bres);
       if(!p || checktag(p, Tcache, QPSUPER))
               panic("morecache: checktag super");
       h = (Cache*)p->iobuf;
       mm = h->wmax;
       putbuf(p);

       wormsidestarts(dev, dskno, &ss);
       ml = ss.sstart;         /* start at beginning of disc side #dskno */
       mh = ml + size;
       if(mh > mm) {
               mh = mm;
               print("limited to %lld\n", (Wideoff)mh-ml);
       }

       count = 0;
       for(m=ml; m < mh; m++) {
               p = getbuf(dev, m, Brd);
               if(p)
                       putbuf(p);
               count++;
       }
       print("%lld blocks loaded from worm %d\n", (Wideoff)count, dskno);
}

void
blockcmp(Device *dev, Off wa, Off ca)
{
       Iobuf *p1, *p2;
       int i, c;

       p1 = getbuf(WDEV(dev), wa, Brd);
       if(!p1) {
               fprint(2, "blockcmp: wdev error\n");
               return;
       }

       p2 = getbuf(CDEV(dev), ca, Brd);
       if(!p2) {
               fprint(2, "blockcmp: cdev error\n");
               putbuf(p1);
               return;
       }

       c = 0;
       for(i=0; i<RBUFSIZE; i++)
               if(p1->iobuf[i] != p2->iobuf[i]) {
                       print("%4d: %.2x %.2x\n",
                               i,
                               p1->iobuf[i]&0xff,
                               p2->iobuf[i]&0xff);
                       c++;
                       if(c >= 10)
                               break;
               }

       putbuf(p1);
       putbuf(p2);
}

void
wblock(Device *dev, Off addr)
{
       Iobuf *p1;
       int i;

       p1 = getbuf(dev, addr, Brd);
       if(p1) {
               i = devwrite(WDEV(dev), addr, p1->iobuf);
               print("i = %d\n", i);
               putbuf(p1);
       }
}

void
cwtest(Device*)
{
}

#ifdef  XXX
/* garbage to change sb size
* probably will need it someday
*/
       fsz = number(0, 0, 10);
       count = 0;
       if(fsz == number(0, -1, 10))
               count = -1;             /* really do it */
       print("fsize = %ld\n", fsz);
       cdev = CDEV(dev);
       cb = getbuf(cdev, CACHE_ADDR, Brd|Bres);
       if(!cb || checktag(cb, Tcache, QPSUPER))
               panic("cwtest: checktag super");
       h = (Cache*)cb->iobuf;
       for(m=0; m<h->msize; m++) {
               p = getbuf(cdev, h->maddr + m/BKPERBLK, Brd|Bmod);
               if(!p || checktag(p, Tbuck, h->maddr + m/BKPERBLK))
                       panic("cwtest: checktag c bucket");
               b = (Bucket*)p->iobuf + m%BKPERBLK;
               ce = b->entry + CEPERBK;
               for(c=b->entry; c<ce; c++) {
                       if(c->waddr < fsz)
                               continue;
                       if(count < 0) {
                               c->state = Cnone;
                               continue;
                       }
                       if(c->state != Cdirty)
                               count++;
               }
               putbuf(p);
       }
       if(count < 0) {
               print("old cache hsize = %ld\n", h->fsize);
               h->fsize = fsz;
               cb->flags |= Bmod;
               p = getbuf(dev, h->sbaddr, Brd|Bmod);
               s = (Superb*)p->iobuf;
               print("old super hsize = %ld\n", s->fsize);
               s->fsize = fsz;
               putbuf(p);
       }
       putbuf(cb);
       print("count = %lld\n", (Wideoff)count);
#endif

int
convstate(char *name)
{
       int i;

       for(i=0; i<nelem(cwnames); i++)
               if(cwnames[i])
                       if(strcmp(cwnames[i], name) == 0)
                               return i;
       return -1;
}

void
searchtag(Device *d, Off a, int tag, int n)
{
       Iobuf *p;
       Tag *t;
       int i;

       if(a == 0)
               a = getstartsb(d);
       p = getbuf(devnone, Cwxx2, 0);
       t = (Tag*)(p->iobuf+BUFSIZE);
       for(i=0; i<n; i++) {
               memset(p->iobuf, 0, RBUFSIZE);
               if(devread(WDEV(d), a+i, p->iobuf)) {
                       if(n == 1000)
                               break;
                       continue;
               }
               if(t->tag == tag) {
                       print("tag %d found at %Z %lld\n", tag, d, (Wideoff)a+i);
                       break;
               }
       }
       putbuf(p);
}

void
cmd_cwcmd(int argc, char *argv[])
{
       Device *dev;
       char *arg;
       char str[28];
       Off s1, s2, a, b, n;
       Cw *cw;

       if(argc <= 1) {
               print("\tcwcmd mvstate state1 state2 [platter]\n");
               print("\tcwcmd prchain [start] [bakflg]\n");
               print("\tcwcmd searchtag [start] [tag] [blocks]\n");
               print("\tcwcmd touchsb\n");
               print("\tcwcmd savecache\n");
               print("\tcwcmd loadcache [dskno]\n");
               print("\tcwcmd morecache dskno [count]\n");
               print("\tcwcmd blockcmp wbno cbno\n");
               print("\tcwcmd startdump [01]\n");
               print("\tcwcmd acct\n");
               print("\tcwcmd clearacct\n");
               return;
       }
       arg = argv[1];

       /*
        * items not depend on a cw filesystem
        */
       if(strcmp(arg, "acct") == 0) {
               for(a=0; a<nelem(growacct); a++) {
                       b = growacct[a];
                       if(b) {
                               uidtostr(str, a, 1);
                               print("%10lld %s\n",
                                       ((Wideoff)b*ADDFREE*RBUFSIZE+500000)/1000000,
                                       str);
                       }
               }
               return;
       }
       if(strcmp(arg, "clearacct") == 0) {
               memset(growacct, 0, sizeof(growacct));
               return;
       }

       /*
        * items depend on cw filesystem
        */
       dev = cons.curfs->dev;
       if(dev == 0 || dev->type != Devcw || dev->private == 0) {
               print("cfs not a cw filesystem: %Z\n", dev);
               return;
       }
       cw = dev->private;
       if(strcmp(arg, "searchtag") == 0) {
               a = 0;
               if(argc > 2)
                       a = number(argv[2], 0, 10);
               b = Tsuper;
               if(argc > 3)
                       b = number(argv[3], 0, 10);
               n = 1000;
               if(argc > 4)
                       n = number(argv[4], 0, 10);
               searchtag(dev, a, b, n);
       } else if(strcmp(arg, "mvstate") == 0) {
               if(argc < 4)
                       goto bad;
               s1 = convstate(argv[2]);
               s2 = convstate(argv[3]);
               if(s1 < 0 || s2 < 0)
                       goto bad;
               a = -1;
               if(argc > 4)
                       a = number(argv[4], 0, 10);
               mvstates(dev, s1, s2, a);
               return;
       bad:
               print("cwcmd mvstate: bad args\n");
       } else if(strcmp(arg, "prchain") == 0) {
               a = 0;
               if(argc > 2)
                       a = number(argv[2], 0, 10);
               s1 = 0;
               if(argc > 3)
                       s1 = number(argv[3], 0, 10);
               prchain(dev, a, s1);
       } else if(strcmp(arg, "touchsb") == 0)
               touchsb(dev);
       else if(strcmp(arg, "savecache") == 0)
               savecache(dev);
       else if(strcmp(arg, "loadcache") == 0) {
               s1 = -1;
               if(argc > 2)
                       s1 = number(argv[2], 0, 10);
               loadcache(dev, s1);
       } else if(strcmp(arg, "morecache") == 0) {
               if(argc <= 2) {
                       print("arg count\n");
                       return;
               }
               s1 = number(argv[2], 0, 10);
               if(argc > 3)
                       s2 = number(argv[3], 0, 10);
               else
                       s2 = wormsizeside(dev, s1); /* default to 1 disc side */
               morecache(dev, s1, s2);
       } else if(strcmp(arg, "blockcmp") == 0) {
               if(argc < 4) {
                       print("cannot arg count\n");
                       return;
               }
               s1 = number(argv[2], 0, 10);
               s2 = number(argv[3], 0, 10);
               blockcmp(dev, s1, s2);
       } else if(strcmp(arg, "startdump") == 0) {
               if(argc > 2)
                       cw->nodump = number(argv[2], 0, 10);
               else
                       cw->nodump = !cw->nodump;
               if(cw->nodump)
                       print("dump stopped\n");
               else
                       print("dump allowed\n");
       } else if(strcmp(arg, "allflag") == 0) {
               if(argc > 2)
                       cw->allflag = number(argv[2], 0, 10);
               else
                       cw->allflag = !cw->allflag;
               print("allflag = %d; falsehits = %lld\n",
                       cw->allflag, (Wideoff)cw->falsehits);
       } else if(strcmp(arg, "storesb") == 0) {
               a = 4168344;
               b = 0;
               if(argc > 2)
                       a = number(argv[2], 4168344, 10);
               if(argc > 3)
                       b = number(argv[3], 0, 10);
               storesb(dev, a, b);
       } else if(strcmp(arg, "test") == 0)
               cwtest(dev);
       else
               print("unknown cwcmd %s\n", arg);
}