#include "all.h"

static  Command command[100];
static  Flag    flag[35];
static  char    statsdef[20];   /* default stats list */
static  int     whoflag;

static  void    consserve1(void *);
static  void    installcmds(void);
static  void    tzinit(char*);

void
consserve(void)
{
       int i;

       snprint(cons.chan->whochan, sizeof(cons.chan->whochan), "console");
       installcmds();
       con_session();
       cmd_exec("cfs");
       cmd_exec("users");
       cmd_exec("version");

       for(i = 0; command[i].arg0; i++)
               if(strcmp("cwcmd", command[i].arg0) == 0){
                       cmd_exec("cwcmd touchsb");
                       break;
               }

       /* switch console output to the cmd pipe */
       dup(0, 1);

       newproc(consserve1, 0, "con");
}

/* console commands process */
static void
consserve1(void *)
{
       char *conline;

       while(conline = Brdline(&bin, '\n')){
               conline[Blinelen(&bin)-1] = '\0';
               cmd_exec(conline);
       }
}

static int
cmdcmp(void *va, void *vb)
{
       Command *a, *b;

       a = va;
       b = vb;
       return strcmp(a->arg0, b->arg0);
}

void
cmd_install(char *arg0, char *help, void (*func)(int, char*[]))
{
       int i;

       qlock(&cons);
       for(i=0; command[i].arg0; i++)
               ;
       if(i >= nelem(command)-2) {
               qunlock(&cons);
               print("cmd_install: too many commands\n");
               return;
       }
       command[i+1].arg0 = 0;
       command[i].help = help;
       command[i].func = func;
       command[i].arg0 = arg0;
       qsort(command, i+1, sizeof(Command), cmdcmp);
       qunlock(&cons);
}

void
cmd_exec(char *arg)
{
       char line[2*Maxword], *s;
       char *argv[10];
       int argc, i, c;

       if(strlen(arg) >= nelem(line)-2) {
               print("cmd_exec: line too long\n");
               return;
       }
       strcpy(line, arg);

       argc = 0;
       s = line;
       c = *s++;
       for(;;) {
               while(isascii(c) && isspace(c))
                       c = *s++;
               if(c == 0)
                       break;
               if(argc >= nelem(argv)-2) {
                       print("cmd_exec: too many args\n");
                       return;
               }
               argv[argc++] = s-1;
               while((!isascii(c) || !isspace(c)) && c != '\0')
                       c = *s++;
               s[-1] = 0;
       }
       if(argc <= 0)
               return;
       for(i=0; s=command[i].arg0; i++)
               if(strcmp(argv[0], s) == 0) {
                       (*command[i].func)(argc, argv);
                       return;
               }
       print("cmd_exec: unknown command: %s\n", argv[0]);
}

static void
cmd_halt(int, char *[])
{
       wlock(&mainlock);       /* halt */
       sync("halt");
       exit();
}

static void
cmd_duallow(int argc, char *argv[])
{
       int uid;

       if(argc <= 1) {
               duallow = 0;
               return;
       }

       uid = strtouid(argv[1]);
       if(uid < 0)
               uid = number(argv[1], -2, 10);
       if(uid < 0) {
               print("bad uid %s\n", argv[1]);
               return;
       }
       duallow = uid;
}

static void
cmd_stats(int argc, char *argv[])
{
       int i, c;
       char buf[30], *s, *p, *q;

       if(argc <= 1) {
               if(statsdef[0] == 0)
                       strcpy(statsdef, "a");
               sprint(buf, "stats s%s", statsdef);
               cmd_exec(buf);
               return;
       }

       strcpy(buf, "stat");
       p = strchr(buf, 0);
       p[1] = 0;

       q = 0;
       for(i = 1; i < argc; i++)
               for(s = argv[i]; c = *s; s++) {
                       if(c == 's')
                               continue;
                       if(c == '-') {
                               q = statsdef;
                               continue;
                       }
                       if(q) {
                               *q++ = c;
                               *q = 0;
                       }
                       *p = c;
                       cmd_exec(buf);
               }
}

static void
cmd_stata(int, char *[])
{
       int i;

       print("cons stats\n");
//      print("\twork =%7W%7W%7W rps\n", cons.work+0, cons.work+1, cons.work+2);
//      print("\trate =%7W%7W%7W tBps\n", cons.rate+0, cons.rate+1, cons.rate+2);
//      print("\thits =%7W%7W%7W iops\n", cons.bhit+0, cons.bhit+1, cons.bhit+2);
//      print("\tread =%7W%7W%7W iops\n", cons.bread+0, cons.bread+1, cons.bread+2);
//      print("\trah  =%7W%7W%7W iops\n", cons.brahead+0, cons.brahead+1, cons.brahead+2);
//      print("\tinit =%7W%7W%7W iops\n", cons.binit+0, cons.binit+1, cons.binit+2);
       print("\tbufs =    %3ld sm %3ld lg %ld res\n",
               cons.nsmall, cons.nlarge, cons.nreseq);

       for(i=0; i<nelem(mballocs); i++)
               if(mballocs[i])
                       print("\t[%d]=%d\n", i, mballocs[i]);

       print("\tioerr=    %3ld wr %3ld ww %3ld dr %3ld dw\n",
               cons.nwormre, cons.nwormwe, cons.nwrenre, cons.nwrenwe);
       print("\tcache=     %9ld hit %9ld miss\n",
               cons.nwormhit, cons.nwormmiss);
}

static int
flagcmp(void *va, void *vb)
{
       Flag *a, *b;

       a = va;
       b = vb;
       return strcmp(a->arg0, b->arg0);
}

ulong
flag_install(char *arg, char *help)
{
       int i;

       qlock(&cons);
       for(i=0; flag[i].arg0; i++)
               ;
       if(i >= 32) {
               qunlock(&cons);
               print("flag_install: too many flags\n");
               return 0;
       }
       flag[i+1].arg0 = 0;
       flag[i].arg0 = arg;
       flag[i].help = help;
       flag[i].flag = 1<<i;
       qsort(flag, i+1, sizeof(Flag), flagcmp);
       qunlock(&cons);
       return 1<<i;
}

void
cmd_flag(int argc, char *argv[])
{
       int f, n, i, j;
       char *s;
       Chan *cp;

       if(argc <= 1) {
               for(i=0; flag[i].arg0; i++)
                       print("%.4lux %s %s\n",
                               flag[i].flag, flag[i].arg0, flag[i].help);
               if(cons.flags)
                       print("flag[*]   = %.4lux\n", cons.flags);
               for(cp = chans; cp; cp = cp->next)
                       if(cp->flags)
                               print("flag[%3d] = %.4lux\n", cp->chan, cp->flags);
               return;
       }

       f = 0;
       n = -1;
       for(i=1; i<argc; i++) {
               for(j=0; s=flag[j].arg0; j++)
                       if(strcmp(s, argv[i]) == 0)
                               goto found;
               j = number(argv[i], -1, 10);
               if(j < 0) {
                       print("bad flag argument: %s\n", argv[i]);
                       continue;
               }
               n = j;
               continue;
       found:
               f |= flag[j].flag;
       }

       if(n < 0) {
               cons.flags ^= f;
               if(f == 0)
                       cons.flags = 0;
               print("flag      = %.8lux\n", cons.flags);
               return;
       }
       for(cp = chans; cp; cp = cp->next)
               if(cp->chan == n) {
                       cp->flags ^= f;
                       if(f == 0)
                               cp->flags = 0;
                       print("flag[%3d] = %.8lux\n", cp->chan, cp->flags);
                       return;
               }
       print("no such channel\n");
}

static void
cmd_who(int argc, char *argv[])
{
       Chan *cp;
       int i, c;

       c = 0;
       for(cp = chans; cp; cp = cp->next) {
               if(cp->whotime == 0 && !(cons.flags & whoflag)) {
                       c++;
                       continue;
               }
               if(argc > 1) {
                       for(i=1; i<argc; i++)
                               if(strcmp(argv[i], cp->whoname) == 0)
                                       break;
                       if(i >= argc) {
                               c++;
                               continue;
                       }
               }
               print("%3d: %10s %24s", cp->chan, cp->whoname, cp->whochan);
               if(cp->whoprint)
                       cp->whoprint(cp);
               print("\n");
       }
       if(c > 0)
               print("%d chans not listed\n", c);
}

static void
cmd_hangup(int argc, char *argv[])
{
       Chan *cp;
       int n;

       if(argc < 2) {
               print("usage: hangup chan-number\n");
               return;
       }
       n = number(argv[1], -1, 10);
       for(cp = chans; cp; cp = cp->next) {
               if(cp->whotime == 0) {
                       if(cp->chan == n)
                               print("that chan is hung up\n");
                       continue;
               }
               if(cp->chan == n)
                       chanhangup(cp, "console command");
       }
}

static void
cmd_sync(int, char *[])
{
       wlock(&mainlock);       /* sync */
       sync("command");
       wunlock(&mainlock);
}

static void
cmd_help(int argc, char *argv[])
{
       char *arg;
       int i, j;

       for(i=0; arg=command[i].arg0; i++) {
               if(argc > 1) {
                       for(j=1; j<argc; j++)
                               if(strcmp(argv[j], arg) == 0)
                                       goto found;
                       continue;
               }
       found:
               print("\t%s %s\n", arg, command[i].help);
       }
}

void
cmd_fstat(int argc, char *argv[])
{
       int i;

       for(i=1; i<argc; i++) {
               if(walkto(argv[i])) {
                       print("cant stat %s\n", argv[i]);
                       continue;
               }
               con_fstat(FID2);
       }
}

void
cmd_create(int argc, char *argv[])
{
       int uid, gid;
       long perm;
       char elem[NAMELEN], *p;

       if(argc < 5) {
               print("usage: create path uid gid mode [lad]\n");
               return;
       }

       p = utfrrune(argv[1], '/');
       if(p) {
               *p++ = 0;
               if(walkto(argv[1])) {
                       print("create failed in walkto: %s\n", p);
                       return;
               }
       } else {
               if(walkto("/"))
                       return;
               p = argv[1];
       }
       if(strlen(p) >= NAMELEN) {
               print("name too long %s\n", p);
               return;
       }

       memset(elem, 0, sizeof(elem));
       strcpy(elem, p);

       uid = strtouid(argv[2]);
       if(uid < -1)
               uid = number(argv[2], -2, 10);
       if(uid < -1) {
               print("bad uid %s\n", argv[2]);
               return;
       }

       gid = strtouid(argv[3]);
       if(gid < -1)
               gid = number(argv[3], -2, 10);
       if(gid < -1) {
               print("bad gid %s\n", argv[3]);
               return;
       }

       perm = number(argv[4], 0777, 8) & 0777;

       if(argc > 5) {
               if(strchr(argv[5], 'l'))
                       perm |= DMEXCL;
               if(strchr(argv[5], 'a'))
                       perm |= DMAPPEND;
               if(strchr(argv[5], 'd'))
                       perm |= DMDIR;
               if(strchr(argv[5], 't'))
                       perm |= DMTMP;
       }

       if(con_create(FID2, elem, uid, gid, perm, 0))
               print("create failed: %s/%s\n", argv[1], p);
}

static void
cmd_clri(int argc, char *argv[])
{
       int i;

       for(i=1; i<argc; i++) {
               if(walkto(argv[i])) {
                       print("cant remove %s\n", argv[i]);
                       continue;
               }
               con_clri(FID2);
       }
}

static void
cmd_allow(int argc, char *argv[])
{
       char *name;
       int uid;

       uid = -1;
       name = "any user";
       if(argc > 1){
               name = argv[1];
               uid = strtouid(name);
               if(uid < 0)
                       uid = number(name, -2, 10);
               if(uid < 0) {
                       print("bad uid %s\n", name);
                       return;
               }
       }
       print("allowed %s\n", name);
       allowed = uid;
}

static void
cmd_disallow(int, char**)
{
       allowed = 0;
}

void
ckblock(Device *d, Off a, int typ, Off qpath)
{
       Iobuf *p;

       if(a) {
               p = getbuf(d, a, Brd);
               if(p) {
                       checktag(p, typ, qpath);
                       putbuf(p);
               }
       }
}

void
doclean(Iobuf *p, Dentry *d, int n, Off a)
{
       int i, mod, typ;
       Off qpath;

       mod = 0;
       qpath = d->qid.path;
       typ = Tfile;
       if(d->mode & DDIR){
               qpath ^= QPDIR;
               typ = Tdir;
       }
       for(i=0; i<NDBLOCK; i++) {
               print("dblock[%d] = %lld\n", i, (Wideoff)d->dblock[i]);
               ckblock(p->dev, d->dblock[i], typ, qpath);
               if(i == n) {
                       d->dblock[i] = a;
                       mod = 1;
                       print("dblock[%d] modified %lld\n", i, (Wideoff)a);
               }
       }

       /* add NDBLOCK so user can cite block address by index */
       for (i = 0; i < NIBLOCK; i++) {
               print("iblocks[%d] = %lld\n", NDBLOCK+i, (Wideoff)d->iblocks[i]);
               ckblock(p->dev, d->iblocks[i], Tind1+i, qpath);
               if(NDBLOCK+i == n) {
                       d->iblocks[i] = a;
                       mod = 1;
                       print("iblocks[%d] modified %lld\n", NDBLOCK+i, (Wideoff)a);
               }
       }

       if(mod)
               p->flags |= Bmod|Bimm;
}

static void
cmd_clean(int argc, char *argv[])
{
       int n;
       Off a;
       Iobuf *p;
       Dentry *d;
       File *f;

       p = 0;
       f = 0;
       while(argc > 1) {
               n = -1;
               if(argc > 2)
                       n = number(argv[2], -1, 10);
               a = 0;
               if(argc > 3)
                       a = number(argv[3], 0, 10);
               if(walkto(argv[1])) {
                       print("cant remove %s\n", argv[1]);
                       break;
               }
               f = filep(cons.chan, FID2, 0);
               if(!f)
                       break;
               if(n >= 0 && f->fs->dev->type == Devro) {
                       print("readonly %s\n", argv[1]);
                       break;
               }
               p = getbuf(f->fs->dev, f->addr, Brd);
               d = getdir(p, f->slot);
               if(!d || !(d->mode & DALLOC)) {
                       print("not alloc %s\n", argv[1]);
                       break;
               }
               doclean(p, d, n, a);
               break;
       }
       if(f)
               qunlock(f);
       if(p)
               putbuf(p);
}

static void
cmd_remove(int argc, char *argv[])
{
       int i;

       for(i=1; i<argc; i++) {
               if(walkto(argv[i])) {
                       print("cant remove %s\n", argv[i]);
                       continue;
               }
               con_remove(FID2);
       }
}

static void
cmd_version(int, char *[])
{
       tzinit("/adm/timezone/local");
       print("%d-bit %s as of %T\n", sizeof(Off)*8 - 1, service, fs_mktime);
       print("\tlast boot %T\n", boottime);
}

static void
cmd_cfs(int argc, char *argv[])
{
       Filsys *fs;
       char *name;

       name = "main";
       if(argc > 1)
               name = argv[1];
       fs = fsstr(name);
       if(fs == 0) {
               print("%s: unknown file system\n", name);
               if(cons.curfs)
                       return;
               fs = &filsys[0];
       }
       if(con_attach(FID1, "adm", fs->name))
               panic("FID1 attach to root");
       cons.curfs = fs;
       print("current fs is \"%s\"\n", cons.curfs->name);
}

static void
cmd_prof(int argc, char *argv[])
{
       int n;
       long m, o;
       char *p;

       if(cons.profbuf == 0) {
               print("no buffer\n");
               return;
       }
       n = !cons.profile;
       if(argc > 1)
               n = number(argv[1], n, 10);
       if(n && !cons.profile) {
               print("clr and start\n");
               memset(cons.profbuf, 0, cons.nprofbuf*sizeof(cons.profbuf[0]));
               cons.profile = 1;
               return;
       }
       if(!n && cons.profile) {
               cons.profile = 0;
               print("stop and write\n");
               if(walkto("/adm/kprofdata"))
                       goto bad;
               if(con_open(FID2, OWRITE|OTRUNC)) {
               bad:
                       print("cant open /adm/kprofdata\n");
                       return;
               }
               p = (char*)cons.profbuf;
               for(m=0; m<cons.nprofbuf; m++) {
                       n = cons.profbuf[m];
                       p[0] = n>>24;
                       p[1] = n>>16;
                       p[2] = n>>8;
                       p[3] = n>>0;
                       p += 4;
               }

               m = cons.nprofbuf*sizeof(cons.profbuf[0]);
               o = 0;
               while(m > 0) {
                       n = 8192;
                       if(n > m)
                               n = m;
                       con_write(FID2, (char*)cons.profbuf+o, o, n);
                       m -= n;
                       o += n;
               }
               return;
       }
}

static void
tzinit(char *file)
{
       char buf[1024];
       Off o;
       int f, n;

       f = create("#e/timezone", OEXCL|OWRITE, 0666);
       if(f < 0)
               return;
       if(walkto(file) || con_open(FID2, 0)) {
               print("tzinit: cannot access %s\n", file);
               close(f);
               remove("#e/timezone");
               return;
       }
       for(o = 0; (n = con_read(FID2, buf, o, sizeof(buf))) > 0; o += n)
               write(f, buf, n);
       close(f);
}


static void
cmd_time(int argc, char *argv[])
{
       int i, len;
       char *cmd;
       Timet t1, t2;

       t1 = time(nil);
       len = 0;
       for(i=1; i<argc; i++)
               len += 1 + strlen(argv[i]);
       cmd = malloc(len + 1);
       cmd[0] = 0;
       for(i=1; i<argc; i++) {
               strcat(cmd, " ");
               strcat(cmd, argv[i]);
       }
       cmd_exec(cmd);
       t2 = time(nil);
       free(cmd);
       print("time = %ld ms\n", TK2MS(t2-t1));
}

void
cmd_noauth(int, char *[])
{
       noauth = !noauth;
       print("auth %s\n", noauth ? "disabled" : "enabled");
}

void
cmd_nonone(int, char *[])
{
       nonone = !nonone;
       print("none %s\n", nonone ? "disabled" : "enabled");
}

void
cmd_noattach(int, char *[])
{
       noattach = !noattach;
       print("attach %s\n", noattach ? "disabled" : "enabled");
}

void
cmd_files(int, char *[])
{
       long i, n;
       Chan *cp;

       for(cp = chans; cp; cp = cp->next)
               cp->nfile = 0;

       lock(&flock);
       n = 0;
       for(i=0; i<conf.nfile; i++)
               if(files[i].cp) {
                       n++;
                       files[i].cp->nfile++;
               }
       print("%ld out of %ld files used\n", n, conf.nfile);
       unlock(&flock);

       n = 0;
       for(cp = chans; cp; cp = cp->next)
               if(cp->nfile) {
                       print("%3d: %5d\n", cp->chan, cp->nfile);
                       n += cp->nfile;
               }
       print("%ld out of %ld files used\n", n, conf.nfile);
}

void
cmd_chatty(int argc, char *argv[])
{
       if(argc < 2) {
               print("cmd_chatty: usage: chatty n\n");
               return;
       }
       chatty = atoi(argv[1]);
}

static void
installcmds(void)
{
       cmd_install("allow", "[uid] -- disable permission checking", cmd_allow);
       cmd_install("cfs", "[file] -- set current filesystem", cmd_cfs);
       cmd_install("chatty", "n -- set chattiness", cmd_chatty);
       cmd_install("clean", "file [bno [addr]] -- block print/fix", cmd_clean);
       cmd_install("check", "[options]", cmd_check);
       cmd_install("clri", "[file ...] -- purge files/dirs", cmd_clri);
       cmd_install("create", "path uid gid perm [lad] -- make a file/dir", cmd_create);
       cmd_install("disallow", "-- (re)enable permission checking", cmd_disallow);
       cmd_install("duallow", "uid -- duallow", cmd_duallow);
       cmd_install("flag", "-- print set flags", cmd_flag);
       cmd_install("fstat", "path -- print info on a file/dir", cmd_fstat);
       cmd_install("halt", "-- return to boot rom", cmd_halt);
       cmd_install("help", "", cmd_help);
       cmd_install("newuser", "username -- add user to /adm/users", cmd_newuser);
       cmd_install("profile", "[01] -- fs profile", cmd_prof);
       cmd_install("remove", "[file ...] -- remove files/dirs", cmd_remove);
       cmd_install("stata", "-- overall stats", cmd_stata);
       cmd_install("stats", "[[-]flags ...] -- various stats", cmd_stats);
       cmd_install("sync", "", cmd_sync);
       cmd_install("time", "command -- time another command", cmd_time);
       cmd_install("users", "[file] -- read /adm/users", cmd_users);
       cmd_install("version", "-- print time of mk and boot", cmd_version);
       cmd_install("who", "[user ...] -- print attaches", cmd_who);
       cmd_install("hangup", "chan -- clunk files", cmd_hangup);
       cmd_install("printconf", "-- print configuration", cmd_printconf);
       cmd_install("noauth", "toggle noauth flag", cmd_noauth);
       cmd_install("nonone", "toggle nonone flag", cmd_nonone);
       cmd_install("noattach", "toggle noattach flag", cmd_noattach);
       cmd_install("files", "report on files structure", cmd_files);

       chatflag = flag_install("chat", "-- verbose");
       errorflag = flag_install("error", "-- on errors");
       whoflag = flag_install("allchans", "-- on who");
       authdebugflag = flag_install("authdebug", "-- report authentications");
}

int
walkto(char *name)
{
       char elem[NAMELEN], *p;
       int n;

       if(con_clone(FID1, FID2))
               return 1;

       for(;;) {
               p = utfrune(name, '/');
               if(p == nil)
                       p = strchr(name, '\0');
               if(p == name) {
                       if(*name == '\0')
                               return 0;
                       name = p+1;
                       continue;
               }
               n = p-name;
               if(n > NAMELEN)
                       return 1;
               memset(elem, 0, sizeof(elem));
               memmove(elem, name, n);
               if(con_walk(FID2, elem))
                       return 1;
               name = p;
       }
}

/* needs to parse and return vlongs to cope with new larger block numbers */
vlong
number(char *arg, int def, int base)
{
       int c, sign, any;
       vlong n;

       if(arg == nil)
               return def;

       sign = 0;
       any = 0;
       n = 0;

       for (c = *arg; isascii(c) && isspace(c) && c != '\n'; c = *arg)
               arg++;
       if(c == '-') {
               sign = 1;
               arg++;
               c = *arg;
       }
       while (isascii(c) && (isdigit(c) || base == 16 && isxdigit(c))) {
               n *= base;
               if(c >= 'a' && c <= 'f')
                       n += c - 'a' + 10;
               else if(c >= 'A' && c <= 'F')
                       n += c - 'A' + 10;
               else
                       n += c - '0';
               arg++;
               c = *arg;
               any = 1;
       }
       if(!any)
               return def;
       if(sign)
               n = -n;
       return n;
}