/*
 * Simple lock server. Linked with server1.
 */

#include "amisc.h"
#include "async.h"
#include "arpc.h"
#include "lock_proto.h"
#include "lock_server.h"

#define DBG 0

LS::LS()
{
}

// start listening to RPCs.
void
LS::attach(ptr<axprt> sx)
{
  sxx = asrv::alloc(sx, lock_prog_1, wrap(LS::dispatch, this));
  c = aclnt::alloc(sx, lock_prog_1);
}

// convert a bit-string to a hex string for debug printouts.
str
LS::hex(str s)
{
//   char buf[64];
//   unsigned int len = s.len();
//   const char *p = s.cstr();
//   unsigned int i;

//   buf[0] = '\0';
//   for(i = 0; i < len && i*2+1 < sizeof(buf); i++){
//     sprintf(buf + (i * 2), "%02x", p[i] & 0xff);
//   }
  
//   return str(buf);
  return s;
}

// done sending a GRANT RPC to a client.
void
LS::grant_done(str name, bool *r, struct sockaddr_in *xsin, clnt_stat err)
{
  delete r;
  delete xsin;
  if(err != 0){
    fprintf(stderr, "grant_done: rpc err %d\n", err);
  }

  lock_status *l = locks[name];
  if (l == NULL)
    fatal << "grant finished for invalid lock " << name << "\n";

  l->granted = true;
  if (l->revoked) 
  {
    send_revoke(l->lockname,
                l->active_request->sin);
  }
}


void
LS::grant_lock(lock_status *lock, lock_request *req) 
{
  if (lock->active_request != NULL)
    fatal << "Attempting to grant an already-granted lock\n";
  if (lock->lockname != req->lockname)
    fatal << "Attempting to grant a lock to a request with a different name\n";

  lock->active_request = req;
  delaycb(0, wrap(this, &LS::send_grant, req->lockname, req->sin));
}

// send a grant RPC to a client.
// the sin argument indicates which client -- it holds the
// client's IP address and port.
// sin should be a copy of the sin that arrived in the acquire()
// handler.
void
LS::send_grant(str name, struct sockaddr_in sin)
{
  bool *r = new bool;
  struct sockaddr_in *xsin = new sockaddr_in;
  *xsin = sin;
#if DBG
  printf("sending grant for %s\n", hex(name).cstr());
#endif
  lname xn;
  xn = name;
  c->call(LOCK_GRANT, &xn, r,
          wrap(this, &LS::grant_done, name, r, xsin),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
          (struct sockaddr *) xsin);
}


// got an acquire RPC from a client.
void
LS::acquire(svccb *sbp)
{
  lname *arg = sbp->template getarg<lname>();
  str name(arg->base(), arg->size());
  bool *res = sbp->template getres<bool>();
  const sockaddr_in sin = *(const sockaddr_in *) sbp->getsa();

#if DBG
  printf("got acquire %s\n", hex(name).cstr());
#endif

  lock_request *req = New lock_request;
  req->lockname = name;
  req->sin = sin;
  
  lock_status *status = locks[name];
  if (status == NULL) {
    status = New lock_status;
    status->lockname = name;
    status->active_request = NULL;
    status->granted = false;
    status->revoked = false;
    locks.insert(status);
    grant_lock(status, req);
  } else {
    if (!status->revoked) 
    {
      status->revoked = true;
      if (status->granted) 
      {
        send_revoke(status->lockname,
                    status->active_request->sin);
      }
      status->queue.insert_tail(req);
    }
  }
  
  *res = true;
  sbp->reply(res);
}

// got a release RPC from a client.
void
LS::release(svccb *sbp)
{
  lname *arg = sbp->template getarg<lname>();
  str name(arg->base(), arg->size());
  bool *res = sbp->template getres<bool>();
  const sockaddr_in sin = *(const sockaddr_in *) sbp->getsa();

#if DBG
  printf("got release %s\n", hex(name).cstr());
#endif

  lock_status *status = locks[name];
  if (status == NULL || status->active_request == NULL) {
    warn << "attempting to release a lock that's not held\n";
    goto reply_and_exit;
  }

  // XXX doesn't check whether the releaser is actually the lock
  // holder.
  delete status->active_request;
  status->active_request = NULL;

  if (status->queue.first != NULL) {
    lock_request *req = status->queue.first;
    status->queue.remove(req);
    status->granted = false;
    status->revoked = (status->queue.first != NULL);
    grant_lock(status, req);
  } else {
    locks.remove(status);
    delete status;
  }
  
reply_and_exit:
  *res = true;
  sbp->reply(res);
}

void
LS::dispatch(LS *bs, svccb *sbp)
{
  switch(sbp->proc()){
  case LOCK_ACQUIRE:
    bs->acquire(sbp);
    break;
  case LOCK_RELEASE:
    bs->release(sbp);
    break;
  default:
    fprintf(stderr, "lock_server: unknown RPC %d\n", sbp->proc());
    const sockaddr_in sin = *(const sockaddr_in *) sbp->getsa();
    fprintf(stderr, "from port %d\n", ntohs(sin.sin_port));
    sbp->reject(PROC_UNAVAIL);
    break;
  }
}

void
LS::send_revoke(str name, struct sockaddr_in sin)
{
  bool *r = new bool;
  struct sockaddr_in *xsin = new sockaddr_in;
  *xsin = sin;
  lname xn;
  xn = name;
#if DBG
  printf("sending revoke for %s\n", hex(name).cstr());
#endif

  c->call(LOCK_REVOKE, &xn, r,
          wrap(this, &LS::dummy_cb, r, xsin),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
                    (struct sockaddr *) xsin);
}

void
LS::dummy_cb(bool *r, struct sockaddr_in *xsin, clnt_stat err)
{                                                                     
  delete r;
  delete xsin;
}                                                                               
