/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1998-2020. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * %CopyrightEnd%
 *

 */
#include <stdlib.h>
#include <string.h>
#include "eidef.h"
#include "eiext.h"
#include "reg.h"
#include "eisend.h"
#include "eirecv.h"
#include "ei_connect_int.h"

static int mn_start_dump(int fd, const erlang_pid *self, 
			 erlang_pid *mnesia, const char *mntab)
{
  char buf[EISMALLBUF];
  char *bufp = buf;
  char tmpbuf[64];
  int index = 0;
  erlang_msg msg;
  int type;
  int arity;
  int version;
  int msglen;
  int i;
  int needlink;
  int needpid;

  /* set up rpc arguments */
  /* { PidFrom, { call, Mod, Fun, Args, user }}  */
  ei_encode_version(buf,&index);
  ei_encode_tuple_header(buf,&index,2);
  ei_encode_pid(buf,&index,self);               /* PidFrom */
  ei_encode_tuple_header(buf,&index,5);
  ei_encode_atom(buf,&index,"call");            /* call */
  ei_encode_atom(buf,&index,EI_MNESIA_MODULE);  /* Mod */
  ei_encode_atom(buf,&index,EI_MNESIA_DUMP);    /* Fun */
  ei_encode_list_header(buf,&index,2);          /* Args: [ table, self() ] */
  ei_encode_atom(buf,&index,mntab);
  ei_encode_pid(buf,&index,self); 
  ei_encode_empty_list(buf,&index);
  ei_encode_atom(buf,&index,"user");            /* user */

  /* make the rpc call */
  if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1;

  /* get the reply: expect link and pid (not sure which will come first though) */
  needlink = needpid = 1;
  while (needlink || needpid) {
    /* get message */
    while (1) {
      index = EISMALLBUF;
      if (!(i = ei_recv_internal(fd,&bufp,&index,&msg,&msglen,1,0))) continue;
      else break;
    }

    switch (i) {
    case ERL_LINK:
      /* got link */
      if (!needlink) return -1;
	needlink = 0;
      break;
      
    case ERL_SEND:
      /* got message - does it contain a pid? */
      if (!needpid) return -1;
      else {
	/* expecting { rex, <pid> } */
	index = 0;
	if (ei_decode_version(buf,&index,&version) 
	    || ei_decode_tuple_header(buf,&index,&arity) 
	    || (arity != 2) 
	    || ei_decode_atom(buf,&index,tmpbuf) 
	    || strcmp(tmpbuf,"rex")
	    || ei_get_type(buf,&index,&type,&arity)
	    || (type != ERL_PID_EXT))
	  return -1; /* bad response from other side */
      
	if (ei_decode_pid(buf,&index,mnesia)) return -1;

	/* got pid */
	needpid = 0;
      }
      break;
      
    default:
      return -1; /* wasn't link or pid */
    }
  }
  return 0;
}

static int mn_send_commit(int fd, erlang_pid *mnesia, erlang_pid *self)
{
  char buf[EISMALLBUF];
  char *bufp=buf;
  char string[256];
  int index = 0;
  int version,arity;
  int msglen;
  erlang_msg msg;
  int i;
  
  /* set up commit message { commit, self() } */
  ei_encode_version(buf,&index);
  ei_encode_tuple_header(buf,&index,2);
  ei_encode_atom(buf,&index,EI_MNESIA_COMMIT);
  ei_encode_pid(buf,&index,self);

  /* send it */
  if (ei_send_encoded(fd,mnesia,buf,index)) return -1;

  /* get reply */
  while (1) {
    index = EISMALLBUF;
    if (!(i=ei_recv_internal(fd,&bufp,&index,&msg,&msglen,1,0))) continue;
    else if (i < 0) return -1;
    else break;
  }
  
  if (i == ERL_SEND) {
    index = 0;
    if (ei_decode_version(buf,&index,&version) 
	|| ei_decode_tuple_header(buf,&index,&arity) 
	|| ei_decode_atom(buf,&index,string))
      return -1;

    if (!strcmp(string,"ok")) return 0;
  }
  /* wrong message type */
  return -1;
}

static int mn_send_delete(int fd, erlang_pid *mnesia, const char *key)
{
  char sbuf[EISMALLBUF];
  char *dbuf = NULL;
  char *msgbuf;
  int index = 0;
  int len = strlen(key) + 32; /* 32 is a slight overestimate */

  if (len > EISMALLBUF)
    if (!(dbuf = malloc(len)))
      return -1;
  msgbuf = (dbuf ? dbuf : sbuf);

  /* set up delete message { delete, Key } */
  ei_encode_version(msgbuf,&index);
  ei_encode_tuple_header(msgbuf,&index,2);
  ei_encode_atom(msgbuf,&index,EI_MNESIA_DELETE);
  ei_encode_string(msgbuf,&index,key);

  /* send it */
  if (ei_send_encoded(fd,mnesia,msgbuf,index)) {
    if (dbuf) free(dbuf);
    return -1;
  }

  if (dbuf) free(dbuf);
  return 0;
}

static int mn_send_write(int fd, erlang_pid *mnesia, const char *key, ei_reg_obj *obj)
{
  char sbuf[EISMALLBUF];
  char *dbuf = NULL;
  char *msgbuf;
  int index = 0;
  int keylen = strlen(key) + 1;
  int len = 32 + keylen + obj->size;

  if (len > EISMALLBUF)
    if (!(dbuf = malloc(len)))
      return -1;
  msgbuf = (dbuf ? dbuf : sbuf);

  ei_encode_version(msgbuf,&index);
  ei_encode_tuple_header(msgbuf,&index,6);
  ei_encode_atom(msgbuf,&index,EI_MNESIA_WRITE);
  ei_encode_string(msgbuf,&index,key);
  ei_encode_long(msgbuf,&index,keylen);
  ei_encode_long(msgbuf,&index,obj->attr);
  ei_encode_long(msgbuf,&index,obj->size);
					      
  switch (ei_reg_typeof(obj)) {
  case EI_INT:
    ei_encode_long(msgbuf,&index,obj->val.i);
    break;
  case EI_FLT:
    ei_encode_double(msgbuf,&index,obj->val.f);
    break;
  case EI_STR:
    if (obj->size > 0) ei_encode_string(msgbuf,&index,obj->val.s);
    else ei_encode_long(msgbuf,&index, 0);  /* just the NULL pointer */
    break;
  case EI_BIN:
    if (obj->size > 0) ei_encode_binary(msgbuf,&index,obj->val.p,obj->size);
    else ei_encode_long(msgbuf,&index, obj->val.i);  /* just the pointer */
    break;
  default:
    if (dbuf) free(dbuf);
    return -1;
  }

  /* send it */
  if (ei_send_encoded(fd,mnesia,msgbuf,index)) {
    if (dbuf) free(dbuf);
    return -1;
  }

  if (dbuf) free(dbuf);
  return 0;
}

static int mn_get_unlink(int fd)
{
  erlang_msg msg;
  char buf[EISMALLBUF];
  char *bufp=buf;
  int index;
  int msglen;
    
  /* wait for unlink or exit */
  while (1) {
    index = EISMALLBUF;
    switch (ei_recv_internal(fd,&bufp,&index,&msg,&msglen,1,0)) {
    case 0: continue;
    case ERL_UNLINK: return 0;
    default: return -1;
    }
  }
  return 0;
}

/* dump to backup */
/* fd is open connection to erlang node */
int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
{
  ei_hash *tab;
  erlang_pid *self;
  erlang_pid mnesia;
  ei_bucket *b;
  ei_reg_obj *obj;
  const char *key;
  ei_cnode *ec;
  int i;
  
  if (!reg || !mntab) return -1; /* return EI_BADARG; */
  tab = reg->tab;
  
  /* make a self pid */
  
  if ((ec = ei_fd_to_cnode(fd)) == NULL) {
      return -1;
  }

  self = ei_self(ec);

  if (mn_start_dump(fd,self,&mnesia,mntab)) return -1;

  /* traverse the table, passing objects to mnesia */
  for (i=0; i<tab->size; i++) {
    b=tab->tab[i];
    while (b) {
      obj = (ei_reg_obj*)(b->value); /* cast to eliminate 'const' warning */
      key = b->key;

      if ((flags & EI_FORCE) || (obj->attr & EI_DIRTY)) {
	if (obj->attr & EI_DELET) {
	  if (mn_send_delete(fd,&mnesia,key)) {
	    ei_send_exit(fd,self,&mnesia,"delete failed");
	    return -1;
	  }
	}
	else {
	  if (mn_send_write(fd,&mnesia,key,obj)) {
	    ei_send_exit(fd,self,&mnesia,"update failed");
	    return -1;
	  }
	}
      }
      b = b->next;
    }
  }

  /* end the transaction */
  if (mn_send_commit(fd,&mnesia,self)) {
    ei_send_exit(fd,self,&mnesia,"commit failed");
    return -1;
  }

  /* wait for unlink */
  if (mn_get_unlink(fd)) return -1;
  
  /* this point only reached if all went ok so far... */
  
  /* now remove all deleted objects, unless the caller asked us not to */
  if (!(flags & EI_NOPURGE)) ei_reg_purge(reg);

  /* success */
  return 0;

}
