/* Copyright © 2003 Russ Cox, MIT; see /sys/src/libsunrpc/COPYING */
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <sunrpc.h>

int chatty;
SunClient *client;

void
usage(void)
{
       fprint(2, "usage: portmap address [cmd]\n"
               "cmd is one of:\n"
               "\tnull\n"
               "\tset prog vers proto port\n"
               "\tunset prog vers proto port\n"
               "\tgetport prog vers proto\n"
               "\tdump (default)\n");
       threadexitsall("usage");
}

void
portCall(SunCall *c, PortCallType type)
{
       c->rpc.prog = PortProgram;
       c->rpc.vers = PortVersion;
       c->rpc.proc = type>>1;
       c->rpc.iscall = !(type&1);
       c->type = type;
}

void
tnull(char **argv)
{
       PortTNull tx;
       PortRNull rx;

       USED(argv);

       memset(&tx, 0, sizeof tx);
       portCall(&tx.call, PortCallTNull);

       memset(&rx, 0, sizeof rx);
       portCall(&rx.call, PortCallRNull);

       if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
               sysfatal("rpc: %r");
}

void
tset(char **argv)
{
       PortTSet tx;
       PortRSet rx;

       memset(&tx, 0, sizeof tx);
       portCall(&tx.call, PortCallTSet);
       tx.map.prog = strtol(argv[0], 0, 0);
       tx.map.vers = strtol(argv[1], 0, 0);
       tx.map.prot = strtol(argv[2], 0, 0);
       tx.map.port = strtol(argv[3], 0, 0);

       memset(&rx, 0, sizeof rx);
       portCall(&rx.call, PortCallRSet);

       if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
               sysfatal("rpc: %r");

       if(rx.b == 0)
               print("rejected\n");
}

void
tunset(char **argv)
{
       PortTUnset tx;
       PortRUnset rx;

       memset(&tx, 0, sizeof tx);
       portCall(&tx.call, PortCallTUnset);
       tx.map.prog = strtol(argv[0], 0, 0);
       tx.map.vers = strtol(argv[1], 0, 0);
       tx.map.prot = strtol(argv[2], 0, 0);
       tx.map.port = strtol(argv[3], 0, 0);

       memset(&rx, 0, sizeof rx);
       portCall(&rx.call, PortCallRUnset);

       if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
               sysfatal("rpc: %r");

       if(rx.b == 0)
               print("rejected\n");
}

void
tgetport(char **argv)
{
       PortTGetport tx;
       PortRGetport rx;

       memset(&tx, 0, sizeof tx);
       portCall(&tx.call, PortCallTGetport);
       tx.map.prog = strtol(argv[0], 0, 0);
       tx.map.vers = strtol(argv[1], 0, 0);
       tx.map.prot = strtol(argv[2], 0, 0);

       memset(&rx, 0, sizeof rx);
       portCall(&rx.call, PortCallRGetport);

       if(sunClientRpc(client, 0, &tx.call, &rx.call, nil) < 0)
               sysfatal("rpc: %r");

       print("%d\n", rx.port);
}

void
tdump(char **argv)
{
       int i;
       uchar *p;
       PortTDump tx;
       PortRDump rx;
       PortMap *m;

       USED(argv);

       memset(&tx, 0, sizeof tx);
       portCall(&tx.call, PortCallTDump);

       memset(&rx, 0, sizeof rx);
       portCall(&rx.call, PortCallRDump);

       if(sunClientRpc(client, 0, &tx.call, &rx.call, &p) < 0)
               sysfatal("rpc: %r");

       for(i=0, m=rx.map; i<rx.nmap; i++, m++)
               print("%ud %ud %ud %ud\n", (uint)m->prog, (uint)m->vers, (uint)m->prot, (uint)m->port);

       free(p);
}

static struct {
       char *cmd;
       int narg;
       void (*fn)(char**);
} tab[] = {
       "null", 0,      tnull,
       "set",  4,      tset,
       "unset",        4,      tunset,
       "getport",      3,      tgetport,
       "dump", 0,      tdump,
};

void
threadmain(int argc, char **argv)
{
       char *dflt[] = { "dump", };
       char *addr, *cmd;
       int i;

       ARGBEGIN{
       case 'R':
               chatty++;
               break;
       }ARGEND

       if(argc < 1)
               usage();

       fmtinstall('B', sunRpcFmt);
       fmtinstall('C', sunCallFmt);
       fmtinstall('H', encodefmt);
       sunFmtInstall(&portProg);

       addr = netmkaddr(argv[0], "udp", "portmap");
       if((client = sunDial(addr)) == nil)
               sysfatal("dial %s: %r", addr);

       client->chatty = chatty;
       sunClientProg(client, &portProg);

       argv++;
       argc--;

       if(argc == 0){
               argc = 1;
               argv = dflt;
       }
       cmd = argv[0];
       argv++;
       argc--;

       for(i=0; i<nelem(tab); i++){
               if(strcmp(tab[i].cmd, cmd) == 0){
                       if(tab[i].narg != argc)
                               usage();
                       (*tab[i].fn)(argv);
                       threadexitsall(nil);
               }
       }
       usage();
}