#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include "strerr.h"
#include "error.h"
#include "str.h"
#include "dns.h"
#include "cdb.h"
#include "ip4.h"
#include "buffer.h"
#include "seek.h"
#include "byte.h"
#include "open.h"
#include "env.h"
#include "scan.h"

#define USAGE " fqdn dynip"
#define FATAL "tinydyndns-update: fatal: "
#define INFO "tinydyndns-update: info: "

char *progname;

void usage () { strerr_die4x(1, "usage: ", progname, USAGE, "\n"); }
void die_nomem() { strerr_die2x(111, FATAL, "out of memory."); }
  
char *dynhost;
char *dynip;
unsigned long ttl =0;
unsigned long ttd =0;
char *loc;

static char *host;
char ip[4];
char data[21];
unsigned int l;

static struct cdb c;
int fdcdb;
int fdtmp;

int main (int argc, char ** argv) {
  int i;
  unsigned int dlen;
  uint32 dpos;
  buffer bcdb;
  char bcdbspace[4096];
  buffer btmp;
  char btmpspace[4096];
  char *s;
  struct tai tt, plus;

  progname =argv[0];
  if (! (dynhost =argv[1])) usage();
  if (! (dynip =argv[2])) usage();
  if (! ip4_scan(dynip, ip)) usage();
  if (! dns_domain_fromdot(&host, dynhost, str_len(dynhost))) die_nomem();
  if ((s =env_get("TTL"))) scan_ulong(s, &ttl);
  if ((s =env_get("TTD"))) scan_ulong(s, &ttd);
  if ((loc =env_get("LOC"))) if (! loc[0] || ! loc[1]) loc =0;

  /* setlock? */

  /* open cdb */
  if ((fdcdb =open_read("data.cdb")) == -1)
    strerr_die2sys(100, FATAL, "unable to open data.cdb: ");
  cdb_init(&c, fdcdb);
  cdb_findstart(&c);

  /* search host */
  for (;;) {
    if(! cdb_findnext(&c, host, dns_domain_length(host))) {
      cdb_free(&c);
      close(fdcdb);
      strerr_die3x(114, FATAL, dynhost, ": not found in data.cdb.");
    }
    dpos =cdb_datapos(&c);
    dlen =cdb_datalen(&c);

    /* check data size */
    if ((dlen != 19) && (dlen != 21)) continue;

    /* read data */
    if (cdb_read(&c, data, dlen, dpos) == -1) {
      cdb_free(&c);
      close(fdcdb);
      strerr_die3sys(111, FATAL, dynhost, ": cdb_read: ");
    }

    if (dlen == 19) if (data[2] != '=') continue;
    if (dlen == 21) {
      if (data[2] != '>') continue;
      if (! loc || ! byte_equal(&data[3], 2, loc)) continue;
    }
    l =dlen -19;

    /* check record type */
    if (byte_equal(data, 2, DNS_T_A)) break;
  }
  cdb_free(&c);

  /* manipulate data */
  if (! ttd) {
    if (byte_equal(&data[15 +l], 4, ip))
      if (byte_diff(&data[3 +l], 4, "\0\0\0\0"))
	strerr_die3x(0, INFO, dynhost, ": IP address not changed.");
    if (! ttl) ttl =5;
    byte_zero(&data[7 +l], 8);
  }
  else {
    tai_now(&tt);
    tai_uint(&plus, ttd);
    tai_add(&tt, &tt, &plus);
    tai_pack(&data[7 +l], &tt);
  }
  uint32_pack_big(&data[3 +l], ttl);
  byte_copy(&data[15 +l], 4, ip);

  /* copy cdb cdb.tmp */
  if (seek_begin(fdcdb) == -1)
    strerr_die3sys(111, FATAL, dynhost, ": seek: ");
  if ((fdtmp =open_trunc("data.cdb.tmp")) == -1)
    strerr_die2sys(111, FATAL, "unable to create data.cdb.tmp: ");
  buffer_init(&bcdb, buffer_unixread, fdcdb, bcdbspace, sizeof bcdbspace);
  buffer_init(&btmp, buffer_unixwrite, fdtmp, btmpspace, sizeof btmpspace);
  while ((i =buffer_feed(&bcdb)) > 0) {
    if (buffer_put(&btmp, buffer_peek(&bcdb), i) == -1) 
      strerr_die2sys(111, FATAL, "unable to write data.cdb.tmp: ");
    buffer_seek(&bcdb, i);
  }
  if (i == -1)
    strerr_die2sys(111, FATAL, "unable to read data.cdb: ");
  if (buffer_flush(&btmp) == -1)
    strerr_die2sys(111, FATAL, "unable to write data.cdb.tmp: ");

  /* manipulate cdb.tmp */
  if (seek_set(fdtmp, dpos) == -1)
    strerr_die3sys(111, FATAL, dynhost, ": seek: ");
  if (write(fdtmp, data, 19 +l) != 19 +l)
    strerr_die3sys(111, FATAL, dynhost, ": write: ");

  if (fsync(fdtmp) == -1)
    strerr_die2sys(111, FATAL, "unable to write data.cdb.tmp: ");
  if (close(fdtmp) == -1)
    strerr_die2sys(111, FATAL, "unable to write data.cdb.tmp: ");
  close(fdcdb);

  /* move cdb.tmp cdb */
  if (rename("data.cdb.tmp", "data.cdb") == -1)
    strerr_die2sys(111, FATAL, "unable to move data.cdb.tmp to data.cdb: ");

  buffer_puts(buffer_1, INFO);
  buffer_puts(buffer_1, "update: ");
  buffer_puts(buffer_1, dynhost);
  buffer_puts(buffer_1, " -> ");
  buffer_puts(buffer_1, dynip);
  buffer_putsflush(buffer_1, "\n");
  _exit(0);
}
